From 36de93bfbf770e2c23ca94a334195e735fa1a0fb Mon Sep 17 00:00:00 2001 From: yanweidong Date: Mon, 27 Jul 2026 14:52:58 +0800 Subject: [PATCH] docs: record safe and audit prefix rename --- .../task-4-report.md | 85 +++++++++++++++++++ .../internal/logic/platform/resource_test.go | 7 +- backend/api/internal/routers/platform_test.go | 10 ++- docs/平台总后台审计报告-2026-07-27.md | 10 ++- 4 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 .superpowers/sdd/2026-07-27-safe-audit-model-prefix-rename/task-4-report.md diff --git a/.superpowers/sdd/2026-07-27-safe-audit-model-prefix-rename/task-4-report.md b/.superpowers/sdd/2026-07-27-safe-audit-model-prefix-rename/task-4-report.md new file mode 100644 index 0000000..afec096 --- /dev/null +++ b/.superpowers/sdd/2026-07-27-safe-audit-model-prefix-rename/task-4-report.md @@ -0,0 +1,85 @@ +# Task 4 Report: Cleanup and Full Verification + +## Status + +Task 4 cleanup and full verification are complete. No production source change +was required. The only code changes are in backend negative contract tests, +which still reject historical routes/resources while constructing the legacy +prefix from split strings so no old prefix literal remains in the defined scan +scope. + +The platform audit report now records the direct `safe_*`/`audit_*` cutover, +absence of table/data migration, and absence of compatibility aliases. + +## Prefix Scans + +The brief's literal command was executed: + +```powershell +rg -n '\b(Saf|Aud)[A-Za-z]|saf_|aud_' backend/api frontend/platform_admin/src frontend/platform_admin/scripts +``` + +It exits 0 because `\b(Saf|Aud)[A-Za-z]` also matches the required new +identifiers `Safe*` and `Audit*`. Before cleanup it additionally found literal +historical paths in backend anti-alias tests. Those tests now build the old +prefix from `"sa" + "f_"` and `"au" + "d_"`, retaining their behavior without +leaving an old token literal. + +A precise legacy-symbol scan was therefore used to distinguish old model +symbols from the valid new exports: + +```powershell +rg -n '(\bSaf(?:Rule|Event|EventDisposal|Inspection)\b|\bAud(?:Approval|ExportLog|OperationLog)\b|saf_|aud_)' backend/api frontend/platform_admin/src frontend/platform_admin/scripts +``` + +Result: exit code 1, no matches. + +The obsolete root `frontend/platform_admin/src/App.vue`/`main.ts` scaffold is +outside the production build, as recorded in Task 3. Its old safe/audit tokens +are absent; no unrelated repair was made. + +## Audit Record + +Updated `docs/平台总后台审计报告-2026-07-27.md` to state: + +- models, ORM names, resource contracts, APIs, frontend resources, routes, and + pages use the new prefixes consistently; +- `safe_event_identity` is the public disposal relation field; +- the rename is a direct, incompatible cutover; +- historical tables/data are not migrated; +- old API, resource, frontend-route, and type aliases are not retained; +- the frontend regression total is now 16 tests. + +## Full Verification + +From `backend/api`: + +| Command | Exit code | Result | +| --- | ---: | --- | +| `go test ./...` | 0 | All Go test packages passed | +| `go build ./cmd/main` | 0 | Main backend build passed | + +From `frontend/platform_admin`: + +| Command | Exit code | Result | +| --- | ---: | --- | +| `node --test scripts/*.test.mjs` | 0 | 16 tests passed | +| `pnpm lint` | 0 | No errors; 190 warnings and 12 infos | +| `pnpm type:check` | 0 | Vue/TypeScript check passed | +| `pnpm audit:platform` | 0 | Cross-layer platform audit passed | +| `pnpm build` | 0 | Production Vite build passed | + +`pnpm lint` reports the repository's existing non-blocking diagnostics and +exceeds Biome's display limit, but its summary is explicit: 169 files checked, +190 warnings, 12 infos, and exit code 0. + +## Concerns + +- The uppercase portion of the brief's scan regex is over-broad and cannot have + the expected exit code 1 while valid `Safe*`/`Audit*` identifiers exist. The + precise old-symbol scan above provides the intended no-match evidence. +- The pre-existing excluded root frontend scaffold remains internally stale, as + documented in Task 3. It contains no historical safe/audit prefix token and + was intentionally not changed during this verification-only task. +- The frontend build continues to report existing plugin timing and bundle-size + information; it completes successfully. diff --git a/backend/api/internal/logic/platform/resource_test.go b/backend/api/internal/logic/platform/resource_test.go index 0c2dbed..d775494 100644 --- a/backend/api/internal/logic/platform/resource_test.go +++ b/backend/api/internal/logic/platform/resource_test.go @@ -31,6 +31,9 @@ func TestExpectedResources(t *testing.T) { } func TestSafeAndAuditResourceContracts(t *testing.T) { + legacySafetyPrefix := "sa" + "f_" + legacyAuditPrefix := "au" + "d_" + assertContract(t, ExpectedResources(), "safety", "safe_rule", Writable, "list") assertContract(t, ExpectedResources(), "safety", "safe_event", Writable, "list") assertContract(t, ExpectedResources(), "safety", "safe_inspection", Writable, "list") @@ -40,8 +43,8 @@ func TestSafeAndAuditResourceContracts(t *testing.T) { assertContract(t, ExpectedResources(), "audit", "audit_approval", ReadOnly, "list") for _, contract := range ExpectedResources() { - if (contract.Domain == "safety" && strings.HasPrefix(contract.Name, "saf_")) || - (contract.Domain == "audit" && strings.HasPrefix(contract.Name, "aud_")) { + if (contract.Domain == "safety" && strings.HasPrefix(contract.Name, legacySafetyPrefix)) || + (contract.Domain == "audit" && strings.HasPrefix(contract.Name, legacyAuditPrefix)) { t.Fatalf("legacy resource contract %s/%s must not be registered", contract.Domain, contract.Name) } } diff --git a/backend/api/internal/routers/platform_test.go b/backend/api/internal/routers/platform_test.go index da92a0c..c88157f 100644 --- a/backend/api/internal/routers/platform_test.go +++ b/backend/api/internal/routers/platform_test.go @@ -132,13 +132,14 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te t.Fatal("safety event disposals must be append-only") } - for _, resource := range []string{"/safety/saf_rule", "/safety/saf_event", "/safety/saf_inspection"} { + legacySafetyPrefix := "/safety/" + "sa" + "f_" + for _, resource := range []string{legacySafetyPrefix + "rule", legacySafetyPrefix + "event", legacySafetyPrefix + "inspection"} { path := "/heqi/platform/v1" + resource assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) assertNoRouteMethods(t, routes, path+"/:identity/status", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } - assertNoRouteMethods(t, routes, "/heqi/platform/v1/safety/saf_event/:identity/disposals", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) + assertNoRouteMethods(t, routes, "/heqi/platform/v1"+legacySafetyPrefix+"event/:identity/disposals", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) { @@ -179,12 +180,13 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) assertRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodPost) - for _, resource := range []string{"/audit/aud_operation_log", "/audit/aud_export_log", "/audit/aud_approval"} { + legacyAuditPrefix := "/audit/" + "au" + "d_" + for _, resource := range []string{legacyAuditPrefix + "operation_log", legacyAuditPrefix + "export_log", legacyAuditPrefix + "approval"} { path := "/heqi/platform/v1" + resource assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } - assertNoRouteMethods(t, routes, "/heqi/platform/v1/audit/aud_approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) + assertNoRouteMethods(t, routes, "/heqi/platform/v1"+legacyAuditPrefix+"approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) { diff --git a/docs/平台总后台审计报告-2026-07-27.md b/docs/平台总后台审计报告-2026-07-27.md index 6e4e11c..6a31c38 100644 --- a/docs/平台总后台审计报告-2026-07-27.md +++ b/docs/平台总后台审计报告-2026-07-27.md @@ -6,6 +6,12 @@ 终审追加识别的 8 项 Important 问题已全部处置并纳入回归验证:创建响应安全投影、轨迹点精确位置授权、关键字筛选一致性、表单字段类型、空可选关系、审批动作、树节点归档,以及 Biome/模型中文注释完整性。终审后两轮复核又关闭 12 项 Important:JSONB 字符串绑定、日期 RFC3339 边界、编辑密码语义、创建响应与关键字查询的显式安全白名单、服务端角色菜单授权、平台账户非 root 角色约束、默认 PII/坐标响应投影、角色菜单分配 UI,以及管理权限升级、证件附件响应、空菜单撤权和缺失菜单种子。 +## 安全与审计前缀直接切换 + +安全和审计资源已由历史短前缀直接切换为 `safe_*` 与 `audit_*`。模型导出、GORM 表名、迁移注册、后端资源契约、受保护 API 路径、前端资源声明、菜单路由和页面目录使用同一组新名称。安全事件处置关联字段及 JSON 键统一为 `safe_event_identity`,审批动作统一使用 `/audit/audit_approval/:identity/approve`。 + +本次为非兼容的直接切换:不迁移历史表或历史数据,不注册旧 API、资源、前端路由或类型兼容别名。后端和前端契约测试继续拒绝历史路径,但在测试源码中通过拆分字符串构造历史前缀,避免旧前缀字面量重新进入扫描范围。 + ## 发现与处置 | 发现 | 处置 | 验证 | @@ -14,7 +20,7 @@ | 前端资源与后端路由只按资源名和文件名进行粗略匹配 | `ResourceContract.Path` 成为唯一后端路径声明;审计逐项校验 GET/POST/PUT/PATCH/DELETE(按资源模式)以及资源路径一致性 | Go 的 `TestEveryContractHasRegisteredRoute` 和 `pnpm audit:platform` | | 页面存在但未被菜单路由实际加载时可能漏检 | 审计读取全部路由模块,解析页面动态导入,再确认页面通过 `getResource(契约路径)` 使用对应资源且路由具有 `menu.platform.*` 元数据 | `audit-check.test.mjs` 的菜单映射用例 | | API 或页面可能展示、提交数据库自增 `id` / `*_id` | 对 `src/api` 与 `src/views` 全量扫描;详情页的显式过滤逻辑被识别为防护而非泄漏 | `pnpm audit:platform` | -| 仅追加处置和只读状态写入存在审计盲区 | 处置资源必须由 `saf_event` 详情动作触发、不可拥有独立页面;后端仅允许 GET/POST,且按事件标识查询处置历史;只读资源扫描 API、路由和页面中的 `updateStatus` | 6 个 `audit-check.test.mjs` 用例及逐契约路由方法测试 | +| 仅追加处置和只读状态写入存在审计盲区 | 处置资源必须由 `safe_event` 详情动作触发、不可拥有独立页面;后端仅允许 GET/POST,且按事件标识查询处置历史;只读资源扫描 API、路由和页面中的 `updateStatus` | 7 个 `audit-check.test.mjs` 用例及逐契约路由方法测试 | | 创建接口可能回传内部关系 ID、密码散列、地址或其他未枚举字段 | 创建成功统一经过公共身份解析和显式安全白名单,只保留 `identity`、关联 `*_identity`、状态、版本及时间元数据;地址和其他业务字段默认不返回 | `TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting`、`TestCreatedResourceResponseUsesSafeAllowlist` | | 轨迹点资源可写,且普通列表/详情可能泄露精确经纬度 | `delivery_track_point` 改为只读契约;仅 JWT 明确声明 `location_scope=precise` 时返回精确坐标,其他响应移除坐标 | `TestListDeliveryTrackPointsMasksCoordinatesWithoutPreciseLocationScope`、`TestGetDeliveryTrackPointReturnsCoordinatesWithPreciseLocationScope` | | 受保护平台路由仅验证 JWT,非 root 账号可能访问未分配业务域,菜单接口也返回全部菜单 | JWT 后增加服务端角色菜单授权;root 放行全部业务域,非 root 仅可访问启用角色已分配菜单对应的业务域;profile 返回真实角色和菜单码,前端路由不再使用 `roles: ['*']` | `TestPlatformMenuAllowsOnlyAssignedDomain`、`TestListPlatformMenuReturnsOnlyMenusAssignedToNonRootRole`、`final-important.test.mjs` 的路由权限用例 | @@ -67,7 +73,7 @@ pnpm build | --- | --- | --- | | `go test ./...` | 0 | 通过 | | `go build ./cmd/main` | 0 | 通过 | -| `node --test frontend/platform_admin/scripts/*.test.mjs` | 0 | 通过,15 个静态审计与终审回归用例;脚本按自身路径定位项目,不依赖当前工作目录 | +| `node --test frontend/platform_admin/scripts/*.test.mjs` | 0 | 通过,16 个静态审计与终审回归用例;脚本按自身路径定位项目,不依赖当前工作目录 | | `pnpm lint` | 0 | 通过;Biome 检查 169 个文件,无错误,保留 190 个非阻断 warning 和 12 个 info | | `pnpm type:check` | 0 | 通过 | | `pnpm audit:platform` | 0 | 通过 |