Repository navigation
feat(archive): version admin review materials - #175
Conversation
📝 WalkthroughWalkthrough新增管理员归档审核材料版本查询、差异比较和恢复 API。恢复会创建新版本、更新 AI 输入版本,并将草稿置为 Changes归档审核材料版本管理
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: 返回差异或更新后的草稿
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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.
| 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]); |
| 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; |
| 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(), | ||
| }) |
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
docs/API.mddocs/openapi.yamlfrontend/src/api/cases.tsfrontend/src/pages/DashboardPage.test.tsxfrontend/src/pages/DashboardPage.tsxsrc/api/models.rssrc/api/routes/admin.rssrc/application/services/case_collaboration_service.rstests/api/archive_drafts_review.rstests/api/openapi_intake_contract.rs
Preview readyThe preview for this build is available at https://pr-175-8d5867b3a395.angui.cg8.site. Temporary demo accounts (all use the password
|
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
docs/openapi.yamlfrontend/src/pages/DashboardPage.test.tsxfrontend/src/pages/DashboardPage.tsxsrc/application/services/case_collaboration_service.rstests/api/archive_drafts_review.rstests/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
| assert_eq!(diff["from_version"], 1); | ||
| assert_eq!(diff["to_version"], 2); | ||
| assert_eq!(diff["added"], json!(["line one", "line one", "line two"])); |
There was a problem hiding this comment.
🎯 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.
| 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
Preview readyThe preview for this build is available at https://pr-175-020e36df0d41.angui.cg8.site. Temporary demo accounts (all use the password
|
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/application/services/case_collaboration_service.rstests/api/archive_drafts_review.rs
Preview readyThe preview for this build is available at https://pr-175-c0499678fc2b.angui.cg8.site. Temporary demo accounts (all use the password
|
- 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
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
deploy/preview/deploy-local.sh
Preview readyThe preview for this build is available at https://pr-175-5b4bb0db0748.angui.cg8.site. Temporary demo accounts (all use the password
|
- 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
There was a problem hiding this comment.
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 ANGUI_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
📒 Files selected for processing (3)
deploy/preview/deploy-local.shsrc/config.rssrc/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- deploy/preview/deploy-local.sh
Preview readyThe preview for this build is available at https://pr-175-b08586d12d64.angui.cg8.site. Temporary demo accounts (all use the password
|
- 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
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/config.rssrc/integrations/ai_gateway.rs
| registered | ||
| .provider | ||
| .build_request(&request)? | ||
| .into_native_request(&endpoint, &credential)?; |
There was a problem hiding this comment.
🔒 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:
- 1: https://docs.rs/http/0.2.12/http/uri/struct.Uri.html
- 2: https://docs.rs/actix-http/latest/actix_http/struct.Uri.html
- 3: https://docs.rs/reqwest/latest/src/reqwest/proxy.rs.html
- 4: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html
- 5: https://docs.rs/http/latest/src/http/uri/scheme.rs.html
- 6: https://docs.rs/http/latest/http/uri/struct.Uri.html
- 7: https://docs.rs/http/latest/http/uri/
- 8: https://github.com/seanmonstar/reqwest/blob/master/src/proxy.rs
- 9: https://docs.rs/reqwest/latest/reqwest/struct.Url.html
- 10: https://docs.rs/reqwest_wasi/latest/reqwest/struct.Url.html
🏁 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.rsRepository: 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.lockRepository: 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
Closes #173
Summary
Verification
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
新功能
改进