Skip to content

feat(archive): version admin review materials - #175

Merged
cg8-5712 merged 6 commits into
mainfrom
feature/issue-173-archive-review-material-history
Aug 2, 2026
Merged

cg8-5712 merged 6 commits into
mainfrom
feature/issue-173-archive-review-material-history

Conversation

@cg8-5712

@cg8-5712 cg8-5712 commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #173

Summary

  • add admin-only review-material version listing, line diff, and immutable restore endpoints
  • ensure archive organization AI uses only administrator-confirmed deidentified material versions
  • display material versions, source scope, selected AI input, diff, and restore controls in the admin dashboard
  • document the contract in OpenAPI and API docs

Verification

  • cargo check --workspace --locked
  • cargo fmt --all -- --check
  • cargo test --locked --test api_contract -- --nocapture (85 passed)
  • npm test -- --run src/pages/DashboardPage.test.tsx (3 passed)
  • npm run build

Existing Test Note

rontend/src/pages/FamilyIntakeForm.test.tsx retains one pre-existing failing fixture: its second-confirmation case omits the now-required name/last-seen-location confirmations, so the UI correctly prevents case creation. This PR does not modify the intake workflow.

Summary by CodeRabbit

  • 新功能

    • 管理员可查看归档审核材料版本及 AI 选中状态。
    • 支持比较不同脱敏版本的内容差异。
    • 可从历史已确认版本创建恢复版本,并填写恢复理由。
    • 恢复后草稿自动回到待审核状态,历史版本保持不变。
    • 审核界面新增版本查看、差异比较、恢复操作及结果反馈。
    • 补充相关 API 文档与 OpenAPI 规范。
  • 改进

    • 部署时新增 AI 提供商配置校验,帮助及时发现无效配置。

Copilot AI review requested due to automatic review settings August 1, 2026 19:26
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

新增管理员归档审核材料版本查询、差异比较和恢复 API。恢复会创建新版本、更新 AI 输入版本,并将草稿置为 pending_review。Dashboard 新增版本展示、差异比较和恢复操作。预览部署脚本增加 AI 配置校验。

Changes

归档审核材料版本管理

Layer / File(s) Summary
API 契约与管理员路由
src/api/models.rs, src/api/routes/admin.rs, docs/openapi.yaml, docs/API.md, frontend/src/api/cases.ts, tests/api/openapi_intake_contract.rs
新增材料列表、版本差异和恢复端点。新增响应模型、恢复请求模型及前端 API 方法。
版本查询、差异与恢复服务
src/application/services/case_collaboration_service.rs
服务层校验权限和材料状态,计算有序行差异,并创建带父版本的新恢复版本。流程更新草稿状态、AI 输入指针和审计记录。
Dashboard 版本审核界面
frontend/src/pages/DashboardPage.tsx, frontend/src/pages/DashboardPage.test.tsx
界面展示材料版本、来源范围和 AI 选中状态,支持版本比较与填写理由后恢复历史版本。测试覆盖相关交互。
归档审核流程测试
tests/api/archive_drafts_review.rs
测试覆盖权限、版本校验、差异查询、恢复版本、草稿状态、跨草稿限制和审计数据。
预览部署 AI 配置校验
deploy/preview/deploy-local.sh, src/config.rs, src/main.rs, src/integrations/ai_gateway.rs
部署脚本通过进程环境传递 AI 配置,并调用 validate-ai-config 校验 provider、endpoint 和 credential。

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant 管理员
  participant DashboardPage
  participant AdminAPI
  participant CaseCollaborationService
  participant AIGateway
  管理员->>DashboardPage: 查看归档草稿
  DashboardPage->>AdminAPI: 获取材料版本列表
  AdminAPI->>CaseCollaborationService: 查询版本并标记 AI 选中版本
  CaseCollaborationService-->>AdminAPI: 返回材料版本
  AdminAPI-->>DashboardPage: 返回版本列表
  管理员->>DashboardPage: 比较或恢复指定版本
  DashboardPage->>AdminAPI: 请求差异或提交恢复理由
  AdminAPI->>CaseCollaborationService: 执行差异查询或版本恢复
  CaseCollaborationService->>AIGateway: 重新组织恢复材料
  CaseCollaborationService-->>DashboardPage: 返回差异或更新后的草稿
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning deploy/preview/deploy-local.sh、src/config.rs、src/main.rs 和 src/integrations/ai_gateway.rs 的 AI 启动配置校验与 #173 无关。 将 AI 运行时配置校验拆分到独立的拉取请求,或提供明确的关联需求和验收标准。
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了管理员归档审核材料版本管理这一主要变更。
Linked Issues check ✅ Passed 变更覆盖了 #173 的版本列表、差异、管理员权限、不可变恢复、AI 输入选择和审计元数据要求。
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-173-archive-review-material-history

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds administrator-only archive review-material version inspection and recovery to support the Issue #173 workflow: admins can list immutable material versions, view line-level diffs, and restore an approved de-identified version as a new immutable version; the admin dashboard and OpenAPI/docs are updated accordingly.

Changes:

  • Added admin API endpoints to list review-material versions, diff two versions, and restore an approved de-identified version as a new immutable version.
  • Wired the admin dashboard to display material versions (including selected AI input), request diffs, and trigger restores.
  • Updated OpenAPI contract tests and API documentation to include the new endpoints/schemas.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/api/openapi_intake_contract.rs Extends OpenAPI contract assertions for the new admin endpoints.
tests/api/archive_drafts_review.rs Adds integration coverage for list/diff/restore material versions and audit metadata.
src/application/services/case_collaboration_service.rs Implements list/diff/restore service logic for archive review-material versions.
src/api/routes/admin.rs Adds admin routes for review-material version listing, diff, and restore.
src/api/models.rs Introduces response/request models for material version list/diff/restore.
frontend/src/pages/DashboardPage.tsx Displays material versions, diff results, and restore controls in the admin dashboard.
frontend/src/pages/DashboardPage.test.tsx Adds UI test coverage for the new admin material-version UI.
frontend/src/api/cases.ts Adds typed frontend API helpers for list/diff/restore endpoints.
docs/openapi.yaml Documents endpoints and schemas in OpenAPI.
docs/API.md Updates human-readable API documentation for the new endpoints.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/pages/DashboardPage.tsx Outdated
Comment on lines +373 to +380
useEffect(() => {
if (!token) return;
void listArchiveReviewMaterials(token, draft.id)
.then(setMaterials)
.catch((cause) =>
setMaterialError(cause instanceof Error ? cause.message : "Unable to load material versions."),
);
}, [draft.id, token]);
Comment on lines +308 to +321
let family_list = test::call_service(
&app,
test::TestRequest::get()
.uri(&format!(
"/api/admin/archive-drafts/{draft_id}/review-materials"
))
.insert_header((
header::AUTHORIZATION,
format!("Bearer {}", context.token(FAMILY).await),
))
.to_request(),
)
.await;
assert_error(family_list, StatusCode::FORBIDDEN, "forbidden").await;
Comment on lines +630 to +638
let from_lines: std::collections::BTreeSet<_> =
from.content.lines().map(str::to_owned).collect();
let to_lines: std::collections::BTreeSet<_> = to.content.lines().map(str::to_owned).collect();
Ok(ArchiveReviewMaterialDiffResponse {
from_version,
to_version,
added: to_lines.difference(&from_lines).cloned().collect(),
removed: from_lines.difference(&to_lines).cloned().collect(),
})

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/DashboardPage.test.tsx (1)

28-39: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

补齐 ../api/cases 的审核操作 mock

DashboardPage.tsx 使用了 deidentifyArchiveDraft 和 reviewArchiveDraft,但 mock 工厂和 vi.hoisted 未提供这两个导出。测试触发对应按钮时,Vitest 会因缺少命名导出而失败。请补充两个 vi.fn() 及转发导出。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/DashboardPage.test.tsx` around lines 28 - 39, 补充
DashboardPage.test.tsx 中 `vi.hoisted` 的 `deidentifyArchiveDraft` 和
`reviewArchiveDraft` 两个 `vi.fn()`,并在 `vi.mock("../api/cases")` 工厂中转发这两个 mock,使
DashboardPage 的对应审核操作按钮可正常执行测试。
🧹 Nitpick comments (2)
frontend/src/pages/DashboardPage.test.tsx (1)

208-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

补充恢复后界面刷新的断言。

测试校验了 restoreArchiveReviewMaterial 的调用参数,但没有校验恢复完成后界面状态。请补充断言,确认恢复后重新请求了版本列表,或确认 selected for AI 标记随之更新。该断言可以固定住我在 frontend/src/pages/DashboardPage.tsx 第 427-434 行提出的状态陈旧问题的修复结果。

As per path instructions:tests/** 需检查改动行为具有有意义的覆盖。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/DashboardPage.test.tsx` around lines 208 - 217, 在
DashboardPage 恢复操作测试中,除校验 restoreArchiveReviewMaterial
参数外,补充恢复完成后的状态断言:确认版本列表被重新请求,或确认 “selected for AI” 标记已更新。使用现有的请求 mock 或界面查询符号覆盖
DashboardPage.tsx 中恢复后的状态刷新行为。

Source: Path instructions

tests/api/archive_drafts_review.rs (1)

396-413: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

补充恢复端点的负向与边界覆盖。

当前测试只覆盖成功路径与 FORBIDDEN。restore_archive_review_material 新增了四个未被覆盖的拒绝分支:

  • 恢复 status == "draft" 的版本(版本 1)应返回 409 conflict。
  • 恢复不存在的版本号应返回 404 not_found。
  • version 为 0 应返回 400 validation_error。
  • reason 为空白字符串应返回 400 validation_error。

同理,diff 端点的 404(版本缺失)分支也未覆盖。请补充这些断言。需要我生成对应测试代码吗?

As per path instructions:tests/** 需检查 negative 与 boundary-case coverage。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/api/archive_drafts_review.rs` around lines 396 - 413, 补充
restore_archive_review_material 测试覆盖:恢复版本 1(status 为 draft)返回 409
CONFLICT,不存在的版本号返回 404 NOT_FOUND,version 为 0 以及空白 reason 分别返回 400
BAD_REQUEST,并断言对应错误类型。同步为 diff 端点补充版本缺失时返回 404 NOT_FOUND 的负向测试,复用现有认证和请求构造方式。

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/openapi.yaml`:
- Around line 1740-1744: 在 diff 端点的 responses 定义中补充 HTTP 400
响应,复用现有的请求参数校验错误响应引用。保留现有 200、401、403 和 404 声明不变,确保 from_version 或 to_version 小于
1 时的 ApiError::Validation 与 OpenAPI 契约一致。

In `@frontend/src/pages/DashboardPage.tsx`:
- Around line 411-421: 将 DashboardPage 中材料版本区域的所有用户可见英文文案统一改为中文,包括“Review
material versions”、版本数量、Restore、Compare with first version、Added、Removed、No line
changes,以及加载失败回退文案“Unable to load material
versions.”;保留现有版本列表、对比和恢复行为不变,并覆盖同一区域的 440-470 行内容。
- Around line 427-434: Update the restore handler around
restoreArchiveReviewMaterial so a successful restore calls both onChanged and
loadMaterials, then refreshes the materials list while preserving the existing
busy-state cleanup and error handling.
- Around line 422-438: 当 draft.status 为 "rejected" 或 "withdrawn" 时隐藏恢复按钮,避免显示因
reason 输入框未渲染而永久禁用的操作。更新 DashboardPage 中 Restore 按钮所在的条件渲染表达式,保留其他状态下现有的
busy、reason 校验及恢复流程。

In `@src/application/services/case_collaboration_service.rs`:
- Around line 630-638: Update the diff construction in the archive review
material response to compare the ordered line sequences from from.content and
to.content instead of collecting them into BTreeSet values. Preserve duplicate
lines and their positions when computing added and removed, using an existing
sequence-diff utility if available; otherwise implement ordered comparison
within the surrounding service method.
- Around line 699-732: 将 case archive 流程中的 gateway.execute
外部调用移出数据库事务:在事务内完成恢复版本写入并提交后再调用 AI,随后用短事务更新草稿并写入 execution_audits;保留现有
ArchiveOrganizationCandidate 校验及确定性回退行为,并检查 deidentify_archive_draft 是否也在事务中调用
gateway.execute,按相同方式拆分事务以避免长时间持有行锁和连接。
- Around line 656-673: 在 restore_archive_review_material 的事务查询之后、读取归档素材之前,显式校验
existing.status 必须为 "draft";对 published、withdrawn、rejected
及其他状态返回适当的冲突错误,阻止后续恢复逻辑修改状态和清除审核记录。保持现有 draft 恢复流程不变。

---

Outside diff comments:
In `@frontend/src/pages/DashboardPage.test.tsx`:
- Around line 28-39: 补充 DashboardPage.test.tsx 中 `vi.hoisted` 的
`deidentifyArchiveDraft` 和 `reviewArchiveDraft` 两个 `vi.fn()`,并在
`vi.mock("../api/cases")` 工厂中转发这两个 mock,使 DashboardPage 的对应审核操作按钮可正常执行测试。

---

Nitpick comments:
In `@frontend/src/pages/DashboardPage.test.tsx`:
- Around line 208-217: 在 DashboardPage 恢复操作测试中,除校验 restoreArchiveReviewMaterial
参数外,补充恢复完成后的状态断言:确认版本列表被重新请求,或确认 “selected for AI” 标记已更新。使用现有的请求 mock 或界面查询符号覆盖
DashboardPage.tsx 中恢复后的状态刷新行为。

In `@tests/api/archive_drafts_review.rs`:
- Around line 396-413: 补充 restore_archive_review_material 测试覆盖:恢复版本 1(status 为
draft)返回 409 CONFLICT,不存在的版本号返回 404 NOT_FOUND,version 为 0 以及空白 reason 分别返回 400
BAD_REQUEST,并断言对应错误类型。同步为 diff 端点补充版本缺失时返回 404 NOT_FOUND 的负向测试,复用现有认证和请求构造方式。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 735e36c0-e68e-42ce-b30f-644f465b48bc

📥 Commits

Reviewing files that changed from the base of the PR and between e3302a5 and 8d5867b.

📒 Files selected for processing (10)
  • docs/API.md
  • docs/openapi.yaml
  • frontend/src/api/cases.ts
  • frontend/src/pages/DashboardPage.test.tsx
  • frontend/src/pages/DashboardPage.tsx
  • src/api/models.rs
  • src/api/routes/admin.rs
  • src/application/services/case_collaboration_service.rs
  • tests/api/archive_drafts_review.rs
  • tests/api/openapi_intake_contract.rs

Comment thread docs/openapi.yaml
Comment thread frontend/src/pages/DashboardPage.tsx Outdated
Comment thread frontend/src/pages/DashboardPage.tsx Outdated
Comment thread frontend/src/pages/DashboardPage.tsx
Comment thread src/application/services/case_collaboration_service.rs Outdated
Comment thread src/application/services/case_collaboration_service.rs
Comment thread src/application/services/case_collaboration_service.rs
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-175-8d5867b3a395.angui.cg8.site.

Temporary demo accounts (all use the password 4f8d9e73eed22e37bbebb1ebf51b25bf1c011578cc950558):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/application/services/case_collaboration_service.rs (1)

683-707: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

并发恢复会产生重复的材料版本号。

第 683-690 行在事务内用 order_by_desc 读取当前最大版本,然后第 692-706 行插入 version = max + 1。该读取没有行锁。两个管理员同时对同一 case_id 执行恢复时,两个事务可以读到相同的最大版本,并插入相同的 version 值。

migration/sql/sqlite/up/0037_add_ai_review_materials.sql(第 42-59 行)以及 mysql、postgres 的对应迁移都只声明了 CHECK (version >= 1),没有 (case_id, version) 的唯一约束。因此重复版本会静默写入。

重复版本会破坏下游查询:

  • diff_archive_review_materials 第 627-628 行用 .find(|item| item.version == ...) 在无序结果上取值,差异结果变为不确定。
  • restore_archive_review_material 第 670-677 行用 .one() 过滤版本,会任意选中其中一行。

建议在 archive_review_materials 上增加 (case_id, version) 唯一索引,并把插入冲突映射为 ApiError::Conflict。唯一约束是这里唯一可靠的串行化手段。

同样的“先读最大值再插入”模式也出现在第 178-183 行和第 296 行,唯一索引可以一并覆盖。

🛡️ 建议的迁移方向
CREATE UNIQUE INDEX idx_archive_review_materials_case_version
    ON archive_review_materials (case_id, version);

As per path instructions:src/** 需审查 concurrency 与 data-loss risks。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/services/case_collaboration_service.rs` around lines 683 -
707, 在 archive_review_materials 的所有迁移中新增 case_id 与 version 的唯一索引,覆盖
restore_archive_review_materials 及第 178-183 行和第 296
行使用的最大版本生成路径;同时更新这些插入流程,将唯一约束冲突映射为 ApiError::Conflict,确保并发恢复不会静默写入重复版本。

Source: Path instructions

🧹 Nitpick comments (1)
src/application/services/case_collaboration_service.rs (1)

317-317: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

提取共享的 AiRequest 构造函数。

第 317 行的 AiRequest 与 restore_archive_review_material 中第 710-724 行的构造完全相同:相同的 system_instruction、archive_candidate_schema()、template_version 为 "case-archive-ai-v2"、redaction_policy_version 为 "archive-approved-material-v1",以及相同的 input_scope_reference 格式。

后续修改提示词或策略版本时必须同时改两处。建议提取一个辅助函数,例如 archive_material_ai_request(material_id: &str, version: i32, content: &str),两处共用。这同时会消除第 317 行的超长单行。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/services/case_collaboration_service.rs` at line 317,
提取共享的归档材料 AiRequest 构造辅助函数,统一封装
system_instruction、archive_candidate_schema、版本字段及 input_scope_reference
等重复配置;更新当前构造位置和 restore_archive_review_material 以调用该辅助函数,并保留各自的错误传播行为,同时拆除第 317
行的超长内联构造。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/application/services/case_collaboration_service.rs`:
- Around line 288-315: 修复 restore_archive_review_material 的材料谱系校验,避免尚未绑定当前草稿的
deidentified 版本可被恢复:在
src/application/services/case_collaboration_service.rs:288-315
提交版本时增加独立中间状态或等效绑定约束,并在
src/application/services/case_collaboration_service.rs:708-709
对恢复版本应用相同约束;恢复流程不得仅依据 status == "deidentified" 放行,且第二个事务失败或乐观更新返回 Conflict
后这些版本必须不可恢复。

In `@tests/api/archive_drafts_review.rs`:
- Around line 529-531: 在相关测试中补充对 ordered_line_diff 结果 diff["removed"]
的非空断言,断言其包含版本 1 的全部行(包括 confirmed clue: 与 completed task: 行)。保留现有 diff["added"]
断言,确保移除分支获得覆盖。

---

Outside diff comments:
In `@src/application/services/case_collaboration_service.rs`:
- Around line 683-707: 在 archive_review_materials 的所有迁移中新增 case_id 与 version
的唯一索引,覆盖 restore_archive_review_materials 及第 178-183 行和第 296
行使用的最大版本生成路径;同时更新这些插入流程,将唯一约束冲突映射为 ApiError::Conflict,确保并发恢复不会静默写入重复版本。

---

Nitpick comments:
In `@src/application/services/case_collaboration_service.rs`:
- Line 317: 提取共享的归档材料 AiRequest 构造辅助函数,统一封装
system_instruction、archive_candidate_schema、版本字段及 input_scope_reference
等重复配置;更新当前构造位置和 restore_archive_review_material 以调用该辅助函数,并保留各自的错误传播行为,同时拆除第 317
行的超长内联构造。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 61c4c7c2-8c06-419f-96db-2ecb14571f39

📥 Commits

Reviewing files that changed from the base of the PR and between 8d5867b and 020e36d.

📒 Files selected for processing (6)
  • docs/openapi.yaml
  • frontend/src/pages/DashboardPage.test.tsx
  • frontend/src/pages/DashboardPage.tsx
  • src/application/services/case_collaboration_service.rs
  • tests/api/archive_drafts_review.rs
  • tests/api/openapi_intake_contract.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/api/openapi_intake_contract.rs
  • frontend/src/pages/DashboardPage.tsx
  • frontend/src/pages/DashboardPage.test.tsx

Comment thread src/application/services/case_collaboration_service.rs
Comment on lines +529 to +531
assert_eq!(diff["from_version"], 1);
assert_eq!(diff["to_version"], 2);
assert_eq!(diff["added"], json!(["line one", "line one", "line two"]));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

补充对 diff["removed"] 的断言。

ordered_line_diff 返回 added 和 removed 两部分。当前文件只断言了 added(第 531 行),removed 在所有测试中都没有被断言。移除分支(服务层第 1590-1593 行)因此没有覆盖。

版本 1 的内容来自 create_archive_draft,包含 confirmed clue: 与 completed task: 行;版本 2 的内容是 "line one\nline one\nline two"。两者没有公共前缀或后缀,因此 removed 应为版本 1 的全部行。增加一条非空断言即可覆盖该分支。

💚 建议补充的断言
     assert_eq!(diff["added"], json!(["line one", "line one", "line two"]));
+    let removed = diff["removed"]
+        .as_array()
+        .expect("diff should return removed lines");
+    assert!(
+        !removed.is_empty(),
+        "replacing the raw material must report removed lines"
+    );

As per path instructions:tests/** 需确认变更行为具备正向、负向与边界覆盖。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert_eq!(diff["from_version"], 1);
assert_eq!(diff["to_version"], 2);
assert_eq!(diff["added"], json!(["line one", "line one", "line two"]));
assert_eq!(diff["from_version"], 1);
assert_eq!(diff["to_version"], 2);
assert_eq!(diff["added"], json!(["line one", "line one", "line two"]));
let removed = diff["removed"]
.as_array()
.expect("diff should return removed lines");
assert!(
!removed.is_empty(),
"replacing the raw material must report removed lines"
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/api/archive_drafts_review.rs` around lines 529 - 531, 在相关测试中补充对
ordered_line_diff 结果 diff["removed"] 的非空断言,断言其包含版本 1 的全部行(包括 confirmed clue: 与
completed task: 行)。保留现有 diff["added"] 断言,确保移除分支获得覆盖。

Source: Path instructions

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-175-020e36df0d41.angui.cg8.site.

Temporary demo accounts (all use the password fd2fcd80f0edfa1e0fd082892ce308899a678a06a8dd699e):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/application/services/case_collaboration_service.rs (1)

708-732: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

处理并发恢复的版本冲突。

archive_review_materials 已有 (case_id, version) 唯一索引。两个事务仍可能读取相同的最大版本,其中一个插入会失败。当前错误会转换为 HTTP 500,而不是 Conflict。请使用案例级串行化或唯一冲突重试,并增加两个草稿并发恢复的集成测试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/application/services/case_collaboration_service.rs` around lines 708 -
732, Update the restoration flow around the next_material_version query and
restored.insert to handle concurrent version allocation for the same case. Use
case-level serialization or retry the transaction on the archive_materials
(case_id, version) unique-key conflict, mapping any unrecoverable conflict to
ApiError::Conflict instead of HTTP 500; add integration coverage for two
concurrent draft restorations and verify both complete with distinct versions.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/application/services/case_collaboration_service.rs`:
- Around line 708-732: Update the restoration flow around the
next_material_version query and restored.insert to handle concurrent version
allocation for the same case. Use case-level serialization or retry the
transaction on the archive_materials (case_id, version) unique-key conflict,
mapping any unrecoverable conflict to ApiError::Conflict instead of HTTP 500;
add integration coverage for two concurrent draft restorations and verify both
complete with distinct versions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ff436bd-df42-45a3-861f-eb521c29a4b4

📥 Commits

Reviewing files that changed from the base of the PR and between 020e36d and c049967.

📒 Files selected for processing (2)
  • src/application/services/case_collaboration_service.rs
  • tests/api/archive_drafts_review.rs

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-175-c0499678fc2b.angui.cg8.site.

Temporary demo accounts (all use the password c66d35a7a8244b4cc4ea55eec40716071ffcb733471466c2):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

- Export AI provider policy, endpoint, and key before Compose interpolation
- Remove AI values from the line-based preview dotenv file
- Preserve formatted JSON policies without splitting them into invalid entries

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/preview/deploy-local.sh`:
- Around line 72-83: 在执行旧预览销毁流程前,更新 `deploy-local.sh` 中围绕 `ai_providers_json`
的校验,使其按 `src/config.rs` 的 `Vec<ProviderConfig>` 规则验证完整 JSON 及 provider
字段。将仅含空白的输入归一化为 `[]` 或直接拒绝,并拒绝无效 JSON、对象及不符合 `ProviderConfig` 的数组;只有校验通过后才继续导出
`ANGUI_AI_PROVIDERS_JSON` 和替换预览。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bcfa42b-09dc-4dc3-8769-64546da989a7

📥 Commits

Reviewing files that changed from the base of the PR and between c049967 and 5b4bb0d.

📒 Files selected for processing (1)
  • deploy/preview/deploy-local.sh

Comment thread deploy/preview/deploy-local.sh
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-175-5b4bb0db0748.angui.cg8.site.

Temporary demo accounts (all use the password f55a188ebb77efccaf1094a125f2adeaab8c9ae7f2212eaf):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

- Add a dedicated command for validating AI provider configuration
- Reuse provider parsing and validation during application startup
- Run validation before replacing the existing preview environment
- Fail deployment early when ANGUI_AI_PROVIDERS_JSON is invalid

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.rs`:
- Around line 149-165: Update validate_ai_provider_configurations_from_env and
the parsing flow around parse_ai_provider_configurations to resolve each
provider’s endpoint_env and credential_env references from the environment,
reject missing or empty values, and validate the endpoint URI and credentials
using the same runtime validation as AiGateway::from_configurations. Preserve
the existing JSON and provider-policy checks, and ensure errors or logs never
include credential values.
- Around line 154-155: Update the ANG​​UI_AI_PROVIDERS_JSON loading in the
provider configuration flow to default only for VarError::NotPresent, while
propagating VarError::NotUnicode as a configuration error. Also replace the
env::var(name).ok() handling in Settings::from_env so non-UTF-8 environment
values are not silently discarded, while preserving normal missing-variable
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2040e948-7f9a-4e80-a7ef-97c06b8ad67a

📥 Commits

Reviewing files that changed from the base of the PR and between 5b4bb0d and b08586d.

📒 Files selected for processing (3)
  • deploy/preview/deploy-local.sh
  • src/config.rs
  • src/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • deploy/preview/deploy-local.sh

Comment thread src/config.rs
Comment thread src/config.rs Outdated
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Preview ready

The preview for this build is available at https://pr-175-b08586d12d64.angui.cg8.site.

Temporary demo accounts (all use the password e4d3bf02ce22545da1ebed1f6ce16e05f9d0c55bcc989a96):

Role Email
Family family@demo.invalid
Commander commander@demo.invalid
Volunteer volunteer@demo.invalid
Learner learner@demo.invalid
Administrator admin@demo.invalid

These accounts and all preview data belong only to this deployment. The next preview deployment resets the SQLite database and invalidates these credentials.

- propagate non-UTF-8 environment variable errors instead of using defaults
- validate configured AI provider endpoints and credentials before startup
- reuse native request materialization for URI and credential validation
- add coverage for environment lookup and runtime configuration failures

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/integrations/ai_gateway.rs`:
- Around line 622-625: 在构建请求的流程中更新 endpoint 校验,确保 AI provider endpoint 必须使用
HTTPS;不要仅依赖可解析为 http::Uri。若已有显式本地开发开关,则仅允许该开关启用时的 loopback HTTP endpoint,否则拒绝所有非
HTTPS endpoint,并为 HTTP endpoint 被拒绝补充测试。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dba3bc0-8f1b-40d2-bb78-b4c4f5af43a8

📥 Commits

Reviewing files that changed from the base of the PR and between b08586d and 5818a59.

📒 Files selected for processing (2)
  • src/config.rs
  • src/integrations/ai_gateway.rs

Comment on lines +622 to +625
registered
.provider
.build_request(&request)?
.into_native_request(&endpoint, &credential)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For the versions of httpandreqwestlocked by this repository, confirm from official documentation whetherhttp::Uriacceptshttp:// URIs and whether reqwest sends requests over HTTP. Also confirm the supported URI schemes.

💡 Result:

Yes, http::Uri accepts URIs with the http scheme [1][2], and reqwest is designed to send requests over the HTTP protocol [3][4]. Regarding the specific details: 1. http::Uri and the http Scheme: The http::Uri type explicitly supports the http scheme [5][1]. Official documentation and examples confirm that a URI string like "http://example.org" can be successfully parsed into an http::Uri instance, and its scheme can be accessed via methods such as scheme() (returning Some(&Scheme::HTTP)) or scheme_str() (returning Some("http")) [6][1][2]. 2. reqwest and HTTP Requests: The reqwest crate is a HTTP client library for Rust, meaning its primary purpose is sending requests over HTTP and HTTPS [3][4]. It utilizes the http crate for request components, including Uri [3][7]. 3. Supported URI Schemes: While http::Uri can represent a wide range of absolute URI schemes, reqwest has specific support for schemes depending on the context: - General Client Requests: reqwest natively handles http and https schemes for standard requests. - Proxy Configuration: When configuring proxies, reqwest explicitly recognizes http and https schemes [3][8][4]. If the socks feature is enabled, it additionally supports socks4, socks4a, socks5, and socks5h schemes for proxy targets [3][8]. Other schemes are not recognized by the built-in proxy logic [8][4]. - Default Port Knowledge: The reqwest internal Url type (which wraps the url crate) explicitly identifies and handles default ports for http, https, ws, wss, and ftp schemes [9][10].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- dependency versions ---'
rg -n '^(name|version) = "(http|reqwest)"$' Cargo.lock Cargo.toml 2>/dev/null || true
printf '%s\n' '--- target source ---'
sed -n '570,640p' src/integrations/ai_gateway.rs
printf '%s\n' '--- related tests ---'
sed -n '1160,1245p' src/integrations/ai_gateway.rs
printf '%s\n' '--- relevant symbols and call sites ---'
rg -n -C 4 'validate_runtime_configuration|into_native_request|build_request|mock_responses|credential' src/integrations/ai_gateway.rs

Repository: AG-Angui/Angui

Length of output: 22489


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- locked http and reqwest packages ---'
sed -n '1300,1340p' Cargo.lock
sed -n '2310,2345p' Cargo.lock
printf '%s\n' '--- URI materialization ---'
sed -n '145,205p' src/integrations/ai_gateway.rs
printf '%s\n' '--- transport call ---'
sed -n '835,885p' src/integrations/ai_gateway.rs
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'reqwest|^http\s*=' Cargo.toml Cargo.lock

Repository: AG-Angui/Angui

Length of output: 7614


阻止非 HTTPS 的 AI provider endpoint。

当前验证只检查 endpoint 是否可解析为 http::Uri。http:// endpoint 会通过验证。执行路径随后会将 credential 放入请求头,并由 reqwest 发送。

验证应拒绝非 HTTPS endpoint。若必须支持本地开发,应通过显式开发开关限制为 loopback endpoint。增加 HTTP endpoint 被拒绝的测试。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/integrations/ai_gateway.rs` around lines 622 - 625, 在构建请求的流程中更新 endpoint
校验,确保 AI provider endpoint 必须使用 HTTPS;不要仅依赖可解析为
http::Uri。若已有显式本地开发开关,则仅允许该开关启用时的 loopback HTTP endpoint,否则拒绝所有非 HTTPS
endpoint,并为 HTTP endpoint 被拒绝补充测试。

Source: Path instructions

@cg8-5712
cg8-5712 merged commit 3f7f2eb into main Aug 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin: archive review material versioning, diff, and restore

2 participants