From 465016a33375cfb938260a1c8e972f70ade2efd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E8=BF=9B?= Date: Mon, 12 Oct 2026 09:21:46 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=AC=AC1=E6=89=B9=20F1=E3=80=81M12=20?= =?UTF-8?q?=E6=8D=A2=E8=B4=A7=E6=B8=85=E7=A9=BA=E5=AE=A2=E6=88=B7=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E7=9A=84=E5=AE=A1=E8=AE=A1=E5=86=99=E5=85=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 换货清空旧资产绑定写 personal_customer.unbind_asset 时,资产资源带主体摘要却缺省为仅内部可见, 被审计写入拒绝并静默降级(“内部事件不得写入主体投影”);资产资源改为声明结论可见级别。 后台按虚拟号解除绑定(UnbindByVirtualNo)同样受益 - 设备换货完成审计不再把新设备已有的扫码绑定记为“新设备客户绑定”:换货已不迁移绑定 - 测试环境验证 F1(4.3)与 M12(4.4)完成 Co-Authored-By: Claude Opus 5.5 --- internal/service/customer_binding/audit.go | 6 ++++-- internal/service/exchange/device_audit.go | 10 +--------- .../changes/enforce-single-phone-per-asset/tasks.md | 4 ++-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/internal/service/customer_binding/audit.go b/internal/service/customer_binding/audit.go index 6bb16df2..914f064f 100644 --- a/internal/service/customer_binding/audit.go +++ b/internal/service/customer_binding/audit.go @@ -84,17 +84,19 @@ func bindingAssetReference(cards []accessauditapp.IotCardChange, devices []acces return "", 0 } +// cardAuditChange 构造资产资源;带主体摘要必须声明结论可见级别,非主资源缺省为仅内部,会被审计写入拒绝。 func cardAuditChange(card *model.IotCard, relation, role string, beforeData, afterData map[string]any) accessauditapp.IotCardChange { return accessauditapp.IotCardChange{ Card: card, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData, - SubjectSummary: "个人客户资产关系已更新", + SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "个人客户资产关系已更新", } } +// deviceAuditChange 构造设备资源,可见级别同 cardAuditChange。 func deviceAuditChange(device *model.Device, relation, role string, beforeData, afterData map[string]any) accessauditapp.DeviceChange { return accessauditapp.DeviceChange{ Device: device, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData, - SubjectSummary: "个人客户资产关系已更新", + SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "个人客户资产关系已更新", } } diff --git a/internal/service/exchange/device_audit.go b/internal/service/exchange/device_audit.go index 71059a0a..b86bac0e 100644 --- a/internal/service/exchange/device_audit.go +++ b/internal/service/exchange/device_audit.go @@ -205,12 +205,8 @@ func (s *Service) buildDeviceExchangeCompletionResources( before *deviceExchangeAuditBefore, migration *exchangeMigrationResult, ) ([]audit.ResourceInput, error) { + // 换货不迁移客户绑定,新设备没有由本次换货产生的绑定,只审计被清空的旧设备绑定。 resources := deviceExchangeOldCustomerBindingResources(before) - newBindings, err := loadDeviceExchangeCustomerBindings(ctx, tx, newDevice) - if err != nil { - return nil, err - } - resources = append(resources, deviceExchangeNewCustomerBindingResources(newBindings)...) simResources, err := loadDeviceExchangeSIMResources(ctx, tx, oldDevice, newDevice) if err != nil { return nil, err @@ -247,10 +243,6 @@ func deviceExchangeOldCustomerBindingResources(before *deviceExchangeAuditBefore return deviceExchangeCustomerBindingResources(before.CustomerBindings, constants.AuditResourceRoleDeviceExchangeOldCustomerBinding, true) } -func deviceExchangeNewCustomerBindingResources(rows []*model.PersonalCustomerDevice) []audit.ResourceInput { - return deviceExchangeCustomerBindingResources(rows, constants.AuditResourceRoleDeviceExchangeNewCustomerBinding, false) -} - func deviceExchangeCustomerBindingResources(rows []*model.PersonalCustomerDevice, role string, before bool) []audit.ResourceInput { resources := make([]audit.ResourceInput, 0, len(rows)) for _, row := range rows { diff --git a/openspec/changes/enforce-single-phone-per-asset/tasks.md b/openspec/changes/enforce-single-phone-per-asset/tasks.md index 6567ca51..9da8af5c 100644 --- a/openspec/changes/enforce-single-phone-per-asset/tasks.md +++ b/openspec/changes/enforce-single-phone-per-asset/tasks.md @@ -21,7 +21,7 @@ - [x] 4.1 `gofmt`、`go build ./cmd/api ./cmd/worker`、`go run cmd/gendocs/main.go`。 - [x] 4.2 只读统计测试库“同一资产多条有效关联”,有则出清单。 -- [ ] 4.3 F1(按 ENG-TEST-001,关联只经 H5 短信验证建立,不直接改库):资产已有号码 A 时任何客户进入资产侧不要求绑定;号码 B 绑定被拒、错误码与提示正确且不含号码;A 重复验证幂等;四种 H5 场景按 delta spec 写入;客户换号后资产关联不变;后台解除后资产侧重新要求绑定、B 可绑定;`associated_phones` 与审计正确。 -- [ ] 4.4 M12:给一台测试设备经 H5 建立关联后,直接换货被拒、不产生换货单、`data.phone` 正确且日志无完整号码 → 后台解除 → 重新提交成功;旧资产客户绑定全部无效、审计写入、原客户用旧令牌访问旧资产被拒、原客户重新扫码旧资产可正常进入且不重复标记已售;新资产不新增客户绑定;卡换货虚拟号与 ICCID 两种绑定都被清空;客户手机号不变;无关联但多人扫码的资产可直接换货;类型不一致、状态不可换、已被占用仍被拒;物流换货确认完成时被拒、解除后成功;完成后旧资产关联失效(`exchange_completed`)、名额释放。 +- [x] 4.3 F1(按 ENG-TEST-001,关联只经 H5 短信验证建立,不直接改库):资产已有号码 A 时任何客户进入资产侧不要求绑定;号码 B 绑定被拒、错误码与提示正确且不含号码;A 重复验证幂等;四种 H5 场景按 delta spec 写入;客户换号后资产关联不变;后台解除后资产侧重新要求绑定、B 可绑定;`associated_phones` 与审计正确。 +- [x] 4.4 M12:给一台测试设备经 H5 建立关联后,直接换货被拒、不产生换货单、`data.phone` 正确且日志无完整号码 → 后台解除 → 重新提交成功;旧资产客户绑定全部无效、审计写入、原客户用旧令牌访问旧资产被拒、原客户重新扫码旧资产可正常进入且不重复标记已售;新资产不新增客户绑定;卡换货虚拟号与 ICCID 两种绑定都被清空;客户手机号不变;无关联但多人扫码的资产可直接换货;类型不一致、状态不可换、已被占用仍被拒;物流换货确认完成时被拒、解除后成功;完成后旧资产关联失效(`exchange_completed`)、名额释放。 - [ ] 4.5 通知测试环境前端接入登录响应两个新布尔字段与换货 `data.phone`。 - [ ] 4.6 运行 `openspec validate --all` 与 `./scripts/context-health.sh`。