fix(operation-trace): repair export URL, drop dead action, prune unused keys

- Export download failed because the URL builder appended the path to a
  "/" base, producing a protocol-relative "//api/..." URL that the browser
  read as host "api"; the request never reached the backend. Resolve the
  base to the current origin, mirroring the other URL builders.
- Remove the orphan ``clear_prune`` action: the branch has no prune/clear
  route and no route rule maps to it, so it could never be produced. Its
  i18n key is removed too.
- Remove ``tamperedCount``, now unreferenced after the verification badge
  was split into integrity/chain counters. The operationTrace catalogue is
  now free of unproduced keys.
- Correct the settings controller docstring: only governance, operation
  logs, filters and export routes exist; there is no on-demand delete.

Verified: no producer for clear_prune, classify() can no longer emit it,
operationTrace orphan scan returns none, tsc/check-i18n/prettier/py_compile
all pass.
This commit is contained in:
TyperBody
2026-09-26 02:43:48 +08:00
parent cf28b5694d
commit abca2e7bff
11 changed files with 18 additions and 30 deletions
@@ -2,12 +2,15 @@
Exposes the "成员操作日志溯源" (member operation traceability) surface:
* ``GET /api/v1/settings/governance`` read level + template table
* ``PUT /api/v1/settings/governance`` change level / retention
* ``GET /api/v1/settings/operation-logs`` page through records
* ``GET /api/v1/settings/operation-logs/filters`` available filter values
* ``POST /api/v1/settings/operation-logs/prune`` enforce retention now
* ``DEL /api/v1/settings/operation-logs`` clear records
* ``GET /api/v1/settings/governance`` read level + template table
* ``PUT /api/v1/settings/governance`` change level / retention
* ``GET /api/v1/settings/operation-logs`` page through records
* ``GET /api/v1/settings/operation-logs/filters`` available filter values
* ``GET /api/v1/settings/operation-logs/export`` download the filtered CSV
Operation records are append-only and there is no route to delete them on
demand: retention is the only deletion path, and it runs automatically when
the governance settings change and from the maintenance loop.
Every route requires ``audit.view``, which is granted to the Workspace owner
and admin only. Write routes additionally require an owning/admin role so a
@@ -212,12 +212,6 @@ ACTION_RULE_TABLE: typing.Final[tuple[ActionRule, ...]] = (
bucket='audit',
resource_type='operation_log',
),
ActionRule(
action='clear_prune',
category='audit',
bucket='write',
resource_type='operation_log',
),
ActionRule(
action='settings_update',
category='settings',
+9 -2
View File
@@ -1867,12 +1867,19 @@ export class BackendClient extends BaseHttpClient {
params.set(key, String(value));
}
const suffix = params.toString();
return `${this.getBaseUrl()}/api/v1/settings/operation-logs/export${
// A base of "/" means "same origin". Appending the path directly produced
// a protocol-relative "//api/..." URL, which a browser reads as the host
// "api" — the export request then never reached the backend. Resolve the
// base to the current origin first, mirroring the other URL builders here.
const apiBase =
this.instance.defaults.baseURL === '/' || !this.instance.defaults.baseURL
? window.location.origin
: this.instance.defaults.baseURL.replace(/\/$/, '');
return `${apiBase}/api/v1/settings/operation-logs/export${
suffix ? `?${suffix}` : ''
}`;
}
public setPassword(
newPassword: string,
currentPassword?: string,
-2
View File
@@ -2752,7 +2752,6 @@ const enUS = {
redacted: 'Redacted',
tamperedBadge: 'Possibly tampered',
verifiedBadge: 'Verified',
tamperedCount: '{{count}} records failed verification',
integrityFailedCount: '{{count}} hash mismatches',
chainFailedCount: '{{count}} broken links',
levels: {
@@ -2778,7 +2777,6 @@ const enUS = {
},
actions: {
audit_log_view: 'View operation logs',
clear_prune: 'Prune records by policy',
settings_update: 'Update tracing settings',
settings_view: 'View tracing settings',
member_invite: 'Invite member',
-2
View File
@@ -2807,7 +2807,6 @@ const esES = {
redacted: 'Oculto',
tamperedBadge: 'Posible manipulación',
verifiedBadge: 'Verificado',
tamperedCount: '{{count}} registros fallaron la verificación',
integrityFailedCount: '{{count}} hashes no coinciden',
chainFailedCount: '{{count}} enlaces rotos',
levels: {
@@ -2833,7 +2832,6 @@ const esES = {
},
actions: {
audit_log_view: 'Ver registros de operación',
clear_prune: 'Depurar registros según la política',
settings_update: 'Actualizar ajustes de trazabilidad',
settings_view: 'Ver ajustes de trazabilidad',
member_invite: 'Invitar miembro',
-2
View File
@@ -2770,7 +2770,6 @@ const jaJP = {
redacted: 'マスク済み',
tamperedBadge: '改ざんの可能性',
verifiedBadge: '検証済み',
tamperedCount: '{{count}} 件が検証に失敗',
integrityFailedCount: '{{count}} 件のハッシュ不一致',
chainFailedCount: '{{count}} 件のリンク断絶',
levels: {
@@ -2796,7 +2795,6 @@ const jaJP = {
},
actions: {
audit_log_view: '操作ログを閲覧',
clear_prune: 'ポリシーで記録を整理',
settings_update: 'トレーサビリティ設定を変更',
settings_view: 'トレーサビリティ設定を閲覧',
member_invite: 'メンバーを招待',
-2
View File
@@ -2777,7 +2777,6 @@ const ruRU = {
redacted: 'Скрыто',
tamperedBadge: 'Возможна подмена',
verifiedBadge: 'Проверено',
tamperedCount: 'Записей с ошибкой проверки: {{count}}',
integrityFailedCount: 'Несовпадение хэша: {{count}}',
chainFailedCount: 'Разрыв связи: {{count}}',
levels: {
@@ -2803,7 +2802,6 @@ const ruRU = {
},
actions: {
audit_log_view: 'Просмотр журнала операций',
clear_prune: 'Очистка записей по политике',
settings_update: 'Изменение настроек трассировки',
settings_view: 'Просмотр настроек трассировки',
member_invite: 'Приглашение участника',
-2
View File
@@ -2704,7 +2704,6 @@ const thTH = {
redacted: 'ปิดบังแล้ว',
tamperedBadge: 'อาจถูกแก้ไข',
verifiedBadge: 'ตรวจสอบผ่าน',
tamperedCount: 'มี {{count}} บันทึกที่ตรวจสอบไม่ผ่าน',
integrityFailedCount: 'แฮชไม่ตรงกัน {{count}} รายการ',
chainFailedCount: 'ลิงก์ขาด {{count}} รายการ',
levels: {
@@ -2730,7 +2729,6 @@ const thTH = {
},
actions: {
audit_log_view: 'ดูบันทึกการดำเนินการ',
clear_prune: 'ล้างบันทึกตามนโยบาย',
settings_update: 'แก้ไขการตั้งค่าการติดตาม',
settings_view: 'ดูการตั้งค่าการติดตาม',
member_invite: 'เชิญสมาชิก',
-2
View File
@@ -2739,7 +2739,6 @@ const viVN = {
redacted: 'Đã ẩn',
tamperedBadge: 'Có thể bị sửa',
verifiedBadge: 'Đã xác minh',
tamperedCount: '{{count}} bản ghi không vượt qua kiểm tra',
integrityFailedCount: '{{count}} mã băm không khớp',
chainFailedCount: '{{count}} liên kết bị đứt',
levels: {
@@ -2765,7 +2764,6 @@ const viVN = {
},
actions: {
audit_log_view: 'Xem nhật ký thao tác',
clear_prune: 'Dọn bản ghi theo chính sách',
settings_update: 'Cập nhật cài đặt truy vết',
settings_view: 'Xem cài đặt truy vết',
member_invite: 'Mời thành viên',
-2
View File
@@ -2601,7 +2601,6 @@ const zhHans = {
redacted: '已脱敏',
tamperedBadge: '可能被篡改',
verifiedBadge: '校验通过',
tamperedCount: '{{count}} 条记录校验异常',
integrityFailedCount: '{{count}} 条哈希不匹配',
chainFailedCount: '{{count}} 条链路断裂',
levels: {
@@ -2627,7 +2626,6 @@ const zhHans = {
},
actions: {
audit_log_view: '查看操作日志',
clear_prune: '按策略清理记录',
settings_update: '修改溯源设置',
settings_view: '查看溯源设置',
member_invite: '邀请成员',
-2
View File
@@ -2602,7 +2602,6 @@ const zhHant = {
redacted: '已遮罩',
tamperedBadge: '可能被竄改',
verifiedBadge: '驗證通過',
tamperedCount: '{{count}} 筆記錄驗證異常',
integrityFailedCount: '{{count}} 筆雜湊不符',
chainFailedCount: '{{count}} 筆鏈結中斷',
levels: {
@@ -2628,7 +2627,6 @@ const zhHant = {
},
actions: {
audit_log_view: '查看操作日誌',
clear_prune: '依策略清理記錄',
settings_update: '修改溯源設定',
settings_view: '查看溯源設定',
member_invite: '邀請成員',