[ { "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. 每个请求必须携带 Header:X-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:562:if 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 '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(¬ification)" ], "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.TaskTypePolling" ], "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:210:return nil, errors.New(errors.CodeNoPaymentConfig, \"暂无可用商户\")", "internal/application/merchantpayment/routing.go:219:return nil, errors.New(errors.CodeNoPaymentConfig, \"暂无可用商户\")", "internal/application/merchantpayment/routing.go:254:return nil, errors.New(errors.CodeNoPaymentConfig, \"暂无可用商户\")" ], "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": "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 'current < 0|Available < 0|ResetConcurrency' internal/service/polling/concurrency_service.go", "literal_output": [ "internal/service/polling/concurrency_service.go:70: if current < 0 {", "internal/service/polling/concurrency_service.go:227:func (s *ConcurrencyService) ResetConcurrency" ], "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 'TaskTypePolling(Realname|Carddata|Package|Protect|CardStatus)' pkg/queue/handler.go", "literal_output": [ "pkg/queue/handler.go:261: h.mux.HandleFunc(constants.TaskTypePollingRealname, realnameHandler.Handle)", "pkg/queue/handler.go:265: h.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 'card_status.*卡状态检查' internal/service/polling/concurrency_service.go", "literal_output": [ "internal/service/polling/concurrency_service.go:287: case \"card_status\": return \"卡状态检查\"" ], "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 \"TriggerHistorical|trigger-approval\" internal/application internal/handler/admin internal/routes", "literal_output": [ "internal/application/refundapproval/creation.go:60:func (s *CreationService) TriggerHistorical(ctx context.Context, refundID uint) (*CreateResult, error) {", "internal/routes/refund.go:52: Register(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": "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=[('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'func (h *PollingPackageHandler) Handle'\"'\"'), ('\"'\"'internal/task/polling_package_handler.go'\"'\"', '\"'\"'h.stopResumeSvc.EvaluateAndAct(ctx, freshCard)'\"'\"'), ('\"'\"'pkg/queue/handler.go'\"'\"', '\"'\"'h.mux.HandleFunc(constants.TaskTypePollingPackage, packageHandler.Handle)'\"'\"'), ('\"'\"'internal/service/iot_card/stop_resume_service.go'\"'\"', '\"'\"'func (s *StopResumeService) isRealnameOK'\"'\"')]; ordered=[]; 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-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.Approve(BodyParser 后直接传 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.Approve(BodyParser 后直接传 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.Approve(BodyParser 后直接传 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": "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/application/distributionwithdrawal/registration.go:64:return nil, errors.New(errors.CodeInvalidParam, \"分销码不可用\")" ], "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" ], "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" ], "domain_state_amount": [ "internal/domain/wallet/wallet.go", "pkg/constants/commission_withdrawal.go", "pkg/constants/approval.go" ], "store_migration_config": [ "internal/store/postgres/commission_withdrawal_request_store.go", "migrations/000215_add_withdrawal_qualification_and_attempt.up.sql" ], "verification": { "command": "sed -n \"120,130p\" internal/application/distributionwithdrawal/withdrawal_approval.go", "literal_output": [ "internal/application/distributionwithdrawal/withdrawal_approval.go:125:\"status\": constants.WithdrawalStatusApproved," ], "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:165:businessErr := errors.New(errors.CodeConflict, \"提现申请已接入企业微信终审,不支持本地人工处理\")", "internal/service/commission_withdrawal/service.go:292:businessErr := 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": [ "internal/store/postgres/withdrawal_attempt_release.go:41:.Where(\"request_id IN ? AND released_at IS NULL\", requestIDs).", "internal/store/postgres/withdrawal_attempt_release.go:50:Where(\"id = ? AND released_at IS NULL\", attempt.ID)." ], "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" ], "handler_consumer_job": [ "internal/routes/refund.go", "internal/handler/admin/refund.go" ], "application_service_query": [ "internal/service/refund/method.go" ], "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": [ "internal/service/refund/method.go: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": [ "internal/application/refundapproval/creation.go:141:BusinessID: attempt.ID, SubmitterAccountID: current.Creator,", "internal/application/refundapproval/creation.go:358:BusinessID: 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": [ "internal/application/refundchannel/service.go: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": [ "internal/service/refund/approval_decision.go:120:originalRoute := 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": [ "pkg/constants/refund.go: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": [ "internal/service/refund/package_usage.go: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": [ "41:\t\t\"收货人姓名\", \"收货人电话\", \"收货地址\", \"快递公司\", \"快递单号\", \"状态\", \"迁移状态\", \"创建人\", \"创建时间\"," ], "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:1031:\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)" ], "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:267:\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:147:\t\taffected, err := s.associationStore.WithTx(tx).InvalidateByIDs(ctx, tx, ids,", "internal/task/phone_asset_unbind_import.go:334:\t\taffected, err := h.associationStore.WithTx(tx).InvalidateByIDs(ctx, tx, ids," ], "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:\tRiskStopped: card.CarrierType == constants.CarrierTypeCBN &&", "internal/application/h5popup/asset.go:113:\tstrings.TrimSpace(card.GatewayExtend) == constants.GatewayCardExtendRiskStop,", "internal/application/h5popup/candidate.go:25:\tvar activeShippingExchangeStatuses = []int{", "internal/application/h5popup/candidate.go:26:\t\tconstants.ExchangeStatusPendingInfo,", "internal/application/h5popup/candidate.go:27:\t\tconstants.ExchangeStatusPendingShip,", "internal/application/h5popup/candidate.go:28:\t\tconstants.ExchangeStatusShipped,", "internal/application/h5popup/candidate.go:29:\t\tconstants.ExchangeStatusCompleted,", "internal/application/h5popup/candidate.go:219:\tWhere(\"old_asset_type = ? AND old_asset_id = ? AND flow_type = ?\", assetType, assetID, constants.ExchangeFlowTypeShipping).", "internal/application/h5popup/candidate.go:220:\tWhere(\"status IN ?\", activeShippingExchangeStatuses).", "internal/application/h5popup/risk_exchange.go:60:\t\tif err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: \"UPDATE\"}).", "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:203:\tOrder(\"priority DESC, updated_at DESC, id DESC\").", "internal/application/h5popup/configuration.go:197:\trecord.Version++", "internal/application/h5popup/configuration.go:199:\trecord.UpdatedAt = now", "internal/application/h5popup/configuration.go:244:\trecord.UpdatedAt = now" ], "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:\tfunc (s *DeliveryService) CreateOrGetPersonal(ctx context.Context, eventID string, customerID uint, request PersonalDirectRequest) (*model.Notification, error) {", "internal/query/notification/query.go:198:\t\tconstants.NotificationTypeH5PopupRiskExchange,", "internal/query/notification/query.go:199:\t\tconstants.NotificationTypeH5PopupOperation,", "internal/application/notification/read.go:220:\t\tconstants.NotificationTypeH5PopupRiskExchange,", "internal/application/notification/read.go:221:\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.00(status 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": "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_enqueue_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[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/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" ], "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 比较后决定回活动态或失败出队)" ], "domain_state_amount": [ "internal/model/polling.go:PollingPriorityItem.AttemptCount/FailureReason/Status/Result", "pkg/constants/polling.go:PollingPriorityMaxAttempts = 3" ], "store_migration_config": [ "internal/store/postgres/polling_priority_item_store.go:IncrementAttempt/ReturnToPending/MarkFailed/MarkCompleted" ], "verification": { "command": "python3 -c 'from pathlib import Path; checks=[(\"pkg/constants/polling.go\", \"const PollingPriorityMaxAttempts = 3\"), (\"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) IncrementAttempt(\"), (\"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(\")]; 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": [ "/api/admin/polling-priority-items", "/api/admin/polling-priority-items/{id}" ], "handler_consumer_job": [ "internal/handler/admin/polling_priority.go:PriorityPollingHandler.Enqueue/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}", "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\")]; 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": [ "/api/admin/polling-priority-items" ], "handler_consumer_job": [ "internal/handler/admin/polling_priority.go:PriorityPollingHandler.Enqueue" ], "application_service_query": [ "internal/service/polling/priority_enqueue_service.go:PriorityEnqueueService.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_enqueue_service.go\", \"func (s *PriorityEnqueueService) Enqueue(\"), (\"internal/service/polling/priority_enqueue_service.go\", \"constants.PollingPriorityTriggerManual\"), (\"internal/service/polling/priority_enqueue_service.go\", \"人工优先入队原因不能为空\"), (\"internal/service/polling/priority_enqueue_service.go\", \"!已达到每日触发次数上限\"), (\"internal/service/polling/priority_enqueue_service.go\", \"!RedisPollingManualDedupeKey\"), (\"internal/service/polling/priority_enqueue_service.go\", \"canManagePollingCard(ctx, s.iotCardStore, cardID)\"), (\"internal/service/polling/priority_enqueue_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[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/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,行为完全等价既有路径)", "四个轮询 Handler:beginPollingPriority 返回 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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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=2),16 与 -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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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.txt):trigger_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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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.txt):3 个独立进程并发触发同一资产仅 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 --all(35 passed/0 failed)、openspec doctor --json(healthy=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 'timeFilterLayout|timeFilterPattern|func ParseTimeRange|FormatTimeFilterValue|TimeFilterFormatError|timeFilterFormatMessageSuffix|timeFilterOrderMessage' pkg/utils/time_range.go", "literal_output": [ "pkg/utils/time_range.go:21:const timeFilterLayout = \"2006-01-02T15:04:05Z07:00\"", "pkg/utils/time_range.go:24:const timeFilterFormatMessageSuffix = \" 时间格式不合法,必须为带时区的 RFC3339 秒级时间,例如 2026-09-01T00:00:00+08:00\"", "pkg/utils/time_range.go:27:const timeFilterOrderMessage = \"start_time 不能晚于 end_time\"", "pkg/utils/time_range.go:34:func ParseTimeRange(start, end string) (*time.Time, *time.Time, error) {", "pkg/utils/time_range.go:50:func FormatTimeFilterValue(value time.Time) string {", "pkg/utils/time_range.go:55:func TimeFilterFormatError(field string) error {", "pkg/utils/time_range.go: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)$`)" ], "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" ], "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 -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/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/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/query/packageexpiry/list.go:72:\t\tStartTime: startTime,", "internal/query/packageexpiry/list.go:241:\t\t// 按当前生效主套餐的最终到期时刻做闭区间比较,含两端。", "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/exporter/order_scene.go:151:\tstart, end, err := strictTimeRange(params.Filters)", "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 {", "internal/store/postgres/asset_allocation_record_store.go:84:\tif createdAtEnd, ok := filters[\"end_time\"].(time.Time); ok {", "internal/service/commission_withdrawal/service.go:68:\t\tStartTime: startTime,", "internal/service/shop_commission/service.go:103:\t\tStartTime: startTime," ], "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 -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": [ "internal/handler/admin/time_params.go:9:// rejectLegacyTimeParams 拒绝受影响端点上已废弃的旧时间参数名。", "internal/handler/admin/time_params.go:12:func rejectLegacyTimeParams(c *fiber.Ctx, keys ...string) error {", "internal/exporter/registry.go:41:\t\tNewExpiringAssetDataSource(db),", "internal/exporter/registry.go:80:\t\tconstants.ExportTaskSceneExpiringAsset:", "internal/handler/admin/asset.go:161:\tif err := rejectLegacyTimeParams(c, \"expires_from\", \"expires_to\"); err != nil {", "internal/handler/admin/asset.go:186:// POST /api/admin/expiring-assets/export", "internal/handler/admin/asset.go:204:\t\tScene: constants.ExportTaskSceneExpiringAsset,", "internal/routes/package_expiry.go:22:\tRegister(router, doc, basePath, \"POST\", \"/expiring-assets/export\", handler.ExportExpiring, RouteSpec{", "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/exporter/expiring_asset_scene.go:25:// NewExpiringAssetDataSource 创建临期资产导出数据源。", "internal/exporter/expiring_asset_scene.go:26:func NewExpiringAssetDataSource(db *gorm.DB) *ExpiringAssetDataSource {", "internal/exporter/expiring_asset_scene.go:32:\treturn constants.ExportTaskSceneExpiringAsset", "internal/exporter/expiring_asset_scene.go:47:\t\t\"店铺\", \"业务员\", \"用户组\", \"资产类型\", \"设备类型\", \"设备型号\", \"资产标识\", \"当前套餐\", \"到期时间\", \"剩余天数\",", "internal/exporter/expiring_asset_scene.go:144:\treturn s.expiryQ.ListAllWithScope(ctx, scope, filter)", "pkg/constants/constants.go:402:\t// ExportTaskSceneExpiringAsset 表示临期资产导出场景,一行对应一项资产,取当前生效主套餐最终到期时间。", "pkg/constants/constants.go:403:\tExportTaskSceneExpiringAsset = \"expiring_asset\"" ], "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 -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/exporter/package_traffic_alert_scene.go:179:\tstartTime, endTime, err := strictTimeRange(params.Filters)", "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)", "internal/exporter/time_filters.go:37:// NormalizeTaskTimeFilters 在创建导出任务时按场景校验并规范化时间边界。", "internal/exporter/time_filters.go:40:func NormalizeTaskTimeFilters(scene string, query map[string]interface{}) error {", "internal/exporter/time_filters.go:64:// ValidateTaskTimeFilters 校验导出任务筛选快照中的时间边界,创建期与执行期共用。", "internal/exporter/time_filters.go:67:func ValidateTaskTimeFilters(scene string, filters map[string]any) error {", "pkg/constants/constants.go:406:// ExportTaskInvalidTimeFilterMessage 是导出任务冻结的时间边界非法时写入 error_message 的安全失败摘要。", "pkg/constants/constants.go:408:const ExportTaskInvalidTimeFilterMessage = \"导出筛选的时间边界非法,任务已终止\"", "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, \"当前账号无可导出的数据范围\")" ], "exit_status": 0 } } ]