From e7b93e463463d23cd64a23b72e4383c26949b12b Mon Sep 17 00:00:00 2001 From: break Date: Wed, 16 Sep 2026 17:33:11 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E9=80=9A=E9=81=93=E6=B5=81=E9=87=8F?= =?UTF-8?q?=E9=98=88=E5=80=BC):=20AUG26-011=20=E5=BD=92=E6=A1=A3=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=B9=B6=E5=90=8C=E6=AD=A5=20carrier-channel-traffic-?= =?UTF-8?q?threshold=20=E4=B8=8E=20package-lifecycle=20=E4=B8=BB=20Spec=20?= =?UTF-8?q?=E5=8F=8A=E8=AF=81=E6=8D=AE=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entry-capability-requirement-matrix.json | 24 ++++-- .../context-reset/requirement-evidence.json | 73 +++++++++++++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../carrier-channel-traffic-threshold/spec.md | 0 .../specs/package-lifecycle/spec.md | 0 .../tasks.md | 0 .../carrier-channel-traffic-threshold/spec.md | 49 +++++++++++++ openspec/specs/package-lifecycle/spec.md | 6 +- 10 files changed, 141 insertions(+), 11 deletions(-) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/.openspec.yaml (100%) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/design.md (100%) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/proposal.md (100%) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/specs/carrier-channel-traffic-threshold/spec.md (100%) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/specs/package-lifecycle/spec.md (100%) rename openspec/changes/{add-carrier-channel-traffic-thresholds => archive/2026-09-16-add-carrier-channel-traffic-thresholds}/tasks.md (100%) create mode 100644 openspec/specs/carrier-channel-traffic-threshold/spec.md diff --git a/docs/verification/context-reset/entry-capability-requirement-matrix.json b/docs/verification/context-reset/entry-capability-requirement-matrix.json index e70aa01..a9ed83b 100644 --- a/docs/verification/context-reset/entry-capability-requirement-matrix.json +++ b/docs/verification/context-reset/entry-capability-requirement-matrix.json @@ -529,7 +529,8 @@ "entry": "GET /api/admin/carriers", "capability": "external-integration", "requirements": [ - "identity-access::数据范围拒绝" + "identity-access::数据范围拒绝", + "carrier-channel-traffic-threshold::通道阈值配置、权限与审计" ], "classification": "behavior" }, @@ -538,7 +539,8 @@ "entry": "GET /api/admin/carriers/{id}", "capability": "external-integration", "requirements": [ - "identity-access::数据范围拒绝" + "identity-access::数据范围拒绝", + "carrier-channel-traffic-threshold::通道阈值配置、权限与审计" ], "classification": "behavior" }, @@ -1865,7 +1867,8 @@ "entry": "POST /api/admin/carriers", "capability": "external-integration", "requirements": [ - "identity-access::数据范围拒绝" + "identity-access::数据范围拒绝", + "carrier-channel-traffic-threshold::通道阈值配置、权限与审计" ], "classification": "behavior" }, @@ -2933,7 +2936,8 @@ "entry": "PUT /api/admin/carriers/{id}", "capability": "external-integration", "requirements": [ - "identity-access::数据范围拒绝" + "identity-access::数据范围拒绝", + "carrier-channel-traffic-threshold::通道阈值配置、权限与审计" ], "classification": "behavior" }, @@ -4358,14 +4362,18 @@ "entry_type": "async", "entry": "constants.TaskTypeCarrierThresholdCycle", "capability": "carrier-channel-traffic-threshold", - "requirements": [], - "classification": "route_index_or_infrastructure" + "requirements": [ + "carrier-channel-traffic-threshold::通道阈值停机与周期恢复" + ], + "classification": "behavior" }, { "entry_type": "async", "entry": "constants.TaskTypeCarrierThresholdRecovery", "capability": "carrier-channel-traffic-threshold", - "requirements": [], - "classification": "route_index_or_infrastructure" + "requirements": [ + "carrier-channel-traffic-threshold::通道阈值停机与周期恢复" + ], + "classification": "behavior" } ] \ No newline at end of file diff --git a/docs/verification/context-reset/requirement-evidence.json b/docs/verification/context-reset/requirement-evidence.json index 81df38f..164ec7f 100644 --- a/docs/verification/context-reset/requirement-evidence.json +++ b/docs/verification/context-reset/requirement-evidence.json @@ -4119,5 +4119,78 @@ "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 端点驱动,真实运营商成功回执路径未端到端验证" + ] + } } ] \ No newline at end of file diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/.openspec.yaml b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/.openspec.yaml similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/.openspec.yaml rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/.openspec.yaml diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/design.md b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/design.md similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/design.md rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/design.md diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/proposal.md b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/proposal.md similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/proposal.md rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/proposal.md diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/specs/carrier-channel-traffic-threshold/spec.md b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/specs/carrier-channel-traffic-threshold/spec.md similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/specs/carrier-channel-traffic-threshold/spec.md rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/specs/carrier-channel-traffic-threshold/spec.md diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/specs/package-lifecycle/spec.md b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/specs/package-lifecycle/spec.md similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/specs/package-lifecycle/spec.md rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/specs/package-lifecycle/spec.md diff --git a/openspec/changes/add-carrier-channel-traffic-thresholds/tasks.md b/openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/tasks.md similarity index 100% rename from openspec/changes/add-carrier-channel-traffic-thresholds/tasks.md rename to openspec/changes/archive/2026-09-16-add-carrier-channel-traffic-thresholds/tasks.md diff --git a/openspec/specs/carrier-channel-traffic-threshold/spec.md b/openspec/specs/carrier-channel-traffic-threshold/spec.md new file mode 100644 index 0000000..c02675f --- /dev/null +++ b/openspec/specs/carrier-channel-traffic-threshold/spec.md @@ -0,0 +1,49 @@ +# carrier-channel-traffic-threshold Specification + +## Purpose + +按运营商通道自身计费周期和累计流量控制停复机,独立于套餐真流量预警。 + +## Requirements + +### Requirement: 通道阈值配置、权限与审计 +仅超级管理员和平台用户 SHALL 在运营商通道新建或编辑时配置流量阈值开关、阈值数值和流量单位;阈值数值必须为正,流量单位必须为系统支持的 MB 或 GB,统计周期为通道既有的上游流量重置日(1~28,上海时区),不另行配置周期。阈值字段仅对平台账号可见:列表与详情响应对非平台账号不返回阈值字段,非平台账号提交包含阈值字段的创建或编辑请求必须被拒绝;不改变通道其余字段的既有可见性与可编辑范围。新增、修改、启用、停用均必须并入通道更新审计的操作者、修改前后字段与时间记录,不另建独立审计动作。停用仅停止后续新锁创建:已持锁卡当前周期继续拒绝复机,新周期解锁与条件复机照常;停用不得删除当前周期锁或历史任务结果。 + +#### Scenario: 越权修改通道阈值 +- **WHEN** 非超级管理员、非平台用户请求创建、编辑或启停通道阈值 +- **THEN** 系统拒绝请求,不修改配置且不产生审计成功事实 + +#### Scenario: 停用后已持锁卡进入新周期 +- **WHEN** 通道阈值停用后,曾达量持锁的卡进入新计费周期 +- **THEN** 系统不再创建新锁,且该卡按新周期规则正常解锁并按条件复机,历史锁与任务结果保留 + +### Requirement: 通道阈值停机与周期恢复 +系统 SHALL 为每个已启用运营商通道按通道既有的上游流量重置日(与网关计数器清零同一事实,上海时区周期边界)判断运营商回传的每张卡当前周期累计流量,累计流量仅取网关累计读数换算,不使用本地用量统计或套餐真流量;每张卡独立判断,不扩散到设备维度,不进入既有停复机的设备扩散路径。达到或超过阈值时,系统为当前周期创建通道阈值停机锁并创建可靠停机任务;同一周期重复判定、并发或任务重放不得重复创建锁或重复发起停机。改阈值不生效于当前周期已持锁卡:当前周期维持拒绝复机,新周期按新阈值判断。 + +持锁卡在当前周期内 MUST 拒绝一切复机,包括自动复机、手动复机、保护期强制复机和机卡分离复机;拒绝时保留锁并记录拒绝事实。停机调用失败或结果未知时保留锁与任务状态,由本能力新增的恢复扫描处理:只查询运营商状态回填、确认成功后补写卡状态;自提交起超过约定窗口仍无法确认结果的,标记异常并转人工处理,不自动删除锁。本能力新增停因 `channel_threshold`,不参与既有自动复机判定,复机仅由周期处理发起。 + +新周期开始时,系统 SHALL 对仍持锁卡解除通道锁;仅存在有效主套餐、流量未耗尽、实名满足、不存在风险停机、销户或其他停机锁时创建自动复机任务,任一条件不满足时只解锁不调用运营商。其他停机锁指卡停机原因为非空且非通道阈值的其他停因,或网关扩展状态为风险停机、销户。复机失败或结果未知同样由恢复扫描处理。卡更换运营商后,旧周期锁保留,新周期按新运营商的重置日计算。 + +#### Scenario: 达量后人工复机 +- **WHEN** 当前计费周期内持有通道阈值停机锁的卡请求复机 +- **THEN** 系统拒绝复机且保留该锁 + +#### Scenario: 新周期仍有其他停机锁 +- **WHEN** 新周期开始的持锁卡没有风险停机但存在其他停机锁 +- **THEN** 系统解除通道阈值锁但不调用运营商复机 + +#### Scenario: 达量后强制复机被拒 +- **WHEN** 当前计费周期内的持锁卡触发保护期强制复机或机卡分离复机 +- **THEN** 系统拒绝复机、保留该锁并记录拒绝事实 + +#### Scenario: 停机结果未知恢复确认 +- **WHEN** 通道阈值停机调用后结果未知,恢复扫描向运营商查询确认卡已停机 +- **THEN** 系统补写卡停机状态并将任务结果记为已确认 + +#### Scenario: 停机结果未知超期转人工 +- **WHEN** 通道阈值停机任务自提交起超过约定窗口仍无法确认结果 +- **THEN** 系统标记异常并留存安全失败原因转人工处理,不自动删除锁 + +#### Scenario: 换运营商后周期归属 +- **WHEN** 持锁卡更换运营商后到达新计费周期 +- **THEN** 旧周期锁保留,新周期按新运营商的上游流量重置日计算并处理 diff --git a/openspec/specs/package-lifecycle/spec.md b/openspec/specs/package-lifecycle/spec.md index d1d538e..b051cb5 100644 --- a/openspec/specs/package-lifecycle/spec.md +++ b/openspec/specs/package-lifecycle/spec.md @@ -8,7 +8,7 @@ 系统 SHALL 按当前套餐和套餐使用状态控制上架、订购、激活、失效与到期处理。主套餐到期时,系统 MUST 先确定同一载体是否存在待生效的后续主套餐:存在时,后续套餐激活与停复机重新评估 MUST 由同一条顺序流程完成;系统 MUST NOT 依据后续套餐激活前的无套餐快照发起停机。后续套餐成功生效后,系统 MUST 依据最新套餐、流量和实名事实重新判断卡网络状态,且不得遗留 `no_package` 停机。不存在后续套餐或后续套餐经业务校验不能生效时,系统 SHALL 按现有停机规则评估卡状态。后续套餐激活结果未知或任务投递失败不得被当作无后续套餐处理并据此停机,系统 SHALL 保留既有激活恢复与轮询兜底路径。 -套餐订单支付成功时,系统 MUST 在支付和套餐权益事务提交后,检查本订单是否存在已生效且未挂靠其他主套餐的主套餐权益。存在时,系统 MUST 基于已提交的套餐、流量、实名和停机原因事实异步尝试自动复机;支付回调不得等待上游复机结果。主套餐权益处于待生效、待实名生效或其他非生效状态时,系统 MUST NOT 因本次支付发起自动复机。自动复机失败 SHALL 保留既有失败记录与轮询兜底机制。 +套餐订单支付成功时,系统 MUST 在支付和套餐权益事务提交后,检查本订单是否存在已生效且未挂靠其他主套餐的主套餐权益。存在时,系统 MUST 基于已提交的套餐、流量、实名和停机原因事实异步尝试自动复机;支付回调不得等待上游复机结果。主套餐权益处于待生效、待实名生效或其他非生效状态时,系统 MUST NOT 因本次支付发起自动复机。载体在当前计费周期内持有运营商通道流量阈值停机锁时,系统 MUST NOT 调用上游复机流程,并 MUST 保留该通道阈值锁与既有网络状态。自动复机失败 SHALL 保留既有失败记录与轮询兜底机制。 #### Scenario: 支付后已生效主套餐触发自动复机 @@ -25,8 +25,8 @@ #### Scenario: 支付后复机条件不满足 - **GIVEN** 套餐订单支付成功后,本订单存在已生效主套餐权益 -- **WHEN** 载体为手动停机、无有效套餐、流量已耗尽或不满足实名策略 -- **THEN** 系统不调用上游复机流程,保留当前网络状态与既有停复机处理路径 +- **WHEN** 载体为手动停机、无有效套餐、流量已耗尽、不满足实名策略,或处于运营商通道流量阈值锁定期 +- **THEN** 系统不调用上游复机流程,保留当前网络状态、既有通道阈值锁与既有停复机处理路径 #### Scenario: 套餐状态流转