From 18717e82764b4b15c1891e31fbca0cff73068ad3 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Thu, 21 May 2026 22:22:25 +0800 Subject: [PATCH 1/3] fix: purge rule version history after reset import --- .../services/impl/ruleConfigServiceImpl.py | 5 ++ .../services/impl/ruleServiceImpl.py | 5 ++ legal-platform-frontend | 2 +- scripts/import_oss_yaml_rules.py | 74 ++++++++++++++++++- tests/test_rule_write_scope.py | 6 ++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py index 028a7dd..d4fe0ac 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py @@ -483,6 +483,7 @@ class RuleConfigServiceImpl(IRuleConfigService): SELECT id, oss_url FROM leaudit_rule_versions WHERE id = ANY(:version_ids) + AND deleted_at IS NULL """ ), {"version_ids": version_ids}, @@ -501,6 +502,7 @@ class RuleConfigServiceImpl(IRuleConfigService): SELECT DISTINCT ON (rule_set_id) rule_set_id, id FROM leaudit_rule_versions WHERE rule_set_id = ANY(:rule_set_ids) + AND deleted_at IS NULL ORDER BY rule_set_id, version_seq DESC, id DESC """ ), @@ -518,6 +520,7 @@ class RuleConfigServiceImpl(IRuleConfigService): SELECT oss_url FROM leaudit_rule_versions WHERE id = :version_id + AND deleted_at IS NULL LIMIT 1 """ ), @@ -676,6 +679,7 @@ class RuleConfigServiceImpl(IRuleConfigService): SELECT id FROM leaudit_rule_versions WHERE rule_set_id = :rule_set_id + AND deleted_at IS NULL ORDER BY version_seq DESC, id DESC LIMIT 1 """ @@ -696,6 +700,7 @@ class RuleConfigServiceImpl(IRuleConfigService): SELECT version_seq FROM leaudit_rule_versions WHERE id = :version_id + AND deleted_at IS NULL LIMIT 1 """ ), diff --git a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py index fb36252..fa881b3 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py @@ -237,6 +237,7 @@ class RuleServiceImpl(IRuleService): rv.published_at FROM leaudit_rule_versions rv WHERE rv.rule_set_id = :rule_set_id + AND rv.deleted_at IS NULL ORDER BY rv.version_seq DESC, rv.id DESC """ ), @@ -258,6 +259,7 @@ class RuleServiceImpl(IRuleService): JOIN leaudit_rule_sets rs ON rs.id = rv.rule_set_id WHERE rs.rule_type = :rule_type AND rs.deleted_at IS NULL + AND rv.deleted_at IS NULL ORDER BY rv.version_seq DESC, rv.id DESC """ ), @@ -281,6 +283,7 @@ class RuleServiceImpl(IRuleService): FROM leaudit_rule_versions rv JOIN leaudit_rule_sets rs ON rs.id = rv.rule_set_id WHERE rv.id = :version_id + AND rv.deleted_at IS NULL LIMIT 1 """ ), @@ -1087,6 +1090,7 @@ class RuleServiceImpl(IRuleService): published_at FROM leaudit_rule_versions WHERE id = :version_id + AND deleted_at IS NULL LIMIT 1 """ ), @@ -1102,6 +1106,7 @@ class RuleServiceImpl(IRuleService): SELECT id FROM leaudit_rule_versions WHERE rule_set_id = :rule_set_id + AND deleted_at IS NULL ORDER BY version_seq DESC, id DESC LIMIT 1 """ diff --git a/legal-platform-frontend b/legal-platform-frontend index fb2fb0b..1e35e98 160000 --- a/legal-platform-frontend +++ b/legal-platform-frontend @@ -1 +1 @@ -Subproject commit fb2fb0b76aae8d302bef370d1fb83c324d056a75 +Subproject commit 1e35e9800602af03c1ee32dce413636cb234e117 diff --git a/scripts/import_oss_yaml_rules.py b/scripts/import_oss_yaml_rules.py index bef7fa6..9ed4f70 100644 --- a/scripts/import_oss_yaml_rules.py +++ b/scripts/import_oss_yaml_rules.py @@ -482,7 +482,66 @@ async def _backup_rule_domain(session) -> Path: return backup_path -async def reset_and_import_rules(root: Path, *, dry_run: bool, prune_oss: bool) -> None: +async def _purge_rule_history() -> int: + async with GetAsyncSession() as session: + await session.execute( + text( + """ + UPDATE leaudit_audit_runs ar + SET rule_version_id = rs.current_version_id, + rule_source_oss_url = COALESCE(current_rv.oss_url, ar.rule_source_oss_url), + rule_source_sha256 = COALESCE(current_rv.file_sha256, ar.rule_source_sha256), + rule_type_id = COALESCE(current_rv.metadata_type_id, ar.rule_type_id) + FROM leaudit_rule_sets rs + LEFT JOIN leaudit_rule_versions current_rv ON current_rv.id = rs.current_version_id + CROSS JOIN leaudit_rule_versions old_rv + WHERE old_rv.id = ar.rule_version_id + AND ar.rule_set_id = rs.id + AND old_rv.id <> rs.current_version_id + AND rs.current_version_id IS NOT NULL + """ + ) + ) + await session.execute( + text( + """ + UPDATE leaudit_rule_results rr + SET rule_version_id = ar.rule_version_id + FROM leaudit_audit_runs ar + CROSS JOIN leaudit_rule_versions old_rv + WHERE old_rv.id = rr.rule_version_id + AND rr.run_id = ar.id + AND old_rv.id <> ar.rule_version_id + """ + ) + ) + result = await session.execute( + text( + """ + DELETE FROM leaudit_rule_versions rv + WHERE NOT EXISTS ( + SELECT 1 + FROM leaudit_rule_sets rs + WHERE rs.current_version_id = rv.id + ) + AND NOT EXISTS ( + SELECT 1 + FROM leaudit_audit_runs ar + WHERE ar.rule_version_id = rv.id + ) + AND NOT EXISTS ( + SELECT 1 + FROM leaudit_rule_results rr + WHERE rr.rule_version_id = rv.id + ) + """ + ) + ) + await session.commit() + return int(result.rowcount or 0) + + +async def reset_and_import_rules(root: Path, *, dry_run: bool, prune_oss: bool, purge_rule_history: bool) -> None: local_rules = load_local_rules(root) canonical_keys = { OssPathUtils.BuildRuleYamlKey(local.rule_type, local.version_no) @@ -572,6 +631,9 @@ async def reset_and_import_rules(root: Path, *, dry_run: bool, prune_oss: bool) print("oss_deleted=0") await import_rules(root, dry_run=False) + if purge_rule_history: + deleted = await _purge_rule_history() + print(f"purged_rule_versions={deleted}") def main() -> None: @@ -580,9 +642,17 @@ def main() -> None: parser.add_argument("--execute", action="store_true") parser.add_argument("--reset-rule-domain", action="store_true") parser.add_argument("--prune-oss", action="store_true") + parser.add_argument("--purge-rule-history", action="store_true") args = parser.parse_args() if args.reset_rule_domain: - asyncio.run(reset_and_import_rules(Path(args.root), dry_run=not args.execute, prune_oss=args.prune_oss)) + asyncio.run( + reset_and_import_rules( + Path(args.root), + dry_run=not args.execute, + prune_oss=args.prune_oss, + purge_rule_history=args.purge_rule_history, + ) + ) else: asyncio.run(import_rules(Path(args.root), dry_run=not args.execute)) diff --git a/tests/test_rule_write_scope.py b/tests/test_rule_write_scope.py index 8a86a69..df821bb 100644 --- a/tests/test_rule_write_scope.py +++ b/tests/test_rule_write_scope.py @@ -105,6 +105,12 @@ def test_assert_rollback_target_allows_previous_version(): service._assert_rollback_target(version_row, rule_set) +def test_rule_version_queries_exclude_soft_deleted_versions(): + sql_text = str(RuleServiceImpl.GetVersions.__code__.co_consts) + + assert "rv.deleted_at IS NULL" in sql_text + + def test_tenant_user_requires_rule_tenant_schema_before_write(): service = RuleServiceImpl() From 370b0a5fa684db507addc0e469912d1627b7f105 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Thu, 21 May 2026 22:23:13 +0800 Subject: [PATCH 2/3] chore: update frontend rule history controls --- legal-platform-frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/legal-platform-frontend b/legal-platform-frontend index 1e35e98..377cd3b 160000 --- a/legal-platform-frontend +++ b/legal-platform-frontend @@ -1 +1 @@ -Subproject commit 1e35e9800602af03c1ee32dce413636cb234e117 +Subproject commit 377cd3b62b7d7301bc2dd1153f4ef6d87e746e2f From 5366868c5fdb222e2b662cd64911b531f89df6cf Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Fri, 22 May 2026 09:26:27 +0800 Subject: [PATCH 3/3] test: expand tenant release smoke coverage --- legal-platform-frontend | 2 +- tests/release/test_g1_rbac_context.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/legal-platform-frontend b/legal-platform-frontend index 377cd3b..f219811 160000 --- a/legal-platform-frontend +++ b/legal-platform-frontend @@ -1 +1 @@ -Subproject commit 377cd3b62b7d7301bc2dd1153f4ef6d87e746e2f +Subproject commit f219811a6e7e13b38813ccf5a3538d968a6e3e9e diff --git a/tests/release/test_g1_rbac_context.py b/tests/release/test_g1_rbac_context.py index d87521a..7bf3ba4 100644 --- a/tests/release/test_g1_rbac_context.py +++ b/tests/release/test_g1_rbac_context.py @@ -25,6 +25,18 @@ def test_g1_admin_auth_and_rbac_context(admin_client: ReleaseApiClient) -> None: assert users_data["total"] >= 1 assert isinstance(users_data["items"], list) + roles_response = admin_client.get("/api/v3/rbac/roles?page=1&page_size=20") + roles_data = ReleaseApiClient.json_data(roles_response) + assert roles_data["items"] + role_id = int(roles_data["items"][0]["id"]) + role_users_response = admin_client.get(f"/api/v3/rbac/roles/{role_id}/users?page=1&page_size=1") + role_users_data = ReleaseApiClient.json_data(role_users_response) + assert role_users_data["page"] == 1 + assert role_users_data["page_size"] == 1 + assert "total" in role_users_data + assert isinstance(role_users_data["items"], list) + assert len(role_users_data["items"]) <= 1 + org_response = admin_client.get("/api/admin/users/organizations/tree?include_users=false") org_data = ReleaseApiClient.json_data(org_response) assert "organizations" in org_data