docs: record safe and audit prefix rename
This commit is contained in:
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 | 通过 |
|
||||
|
||||
Reference in New Issue
Block a user