Skip to content

feat: two-phase intake assessments with AMap routing - #116

Merged
cg8-5712 merged 10 commits into
mainfrom
fix/issues-5-6-7
Jul 25, 2026
Merged

cg8-5712 merged 10 commits into
mainfrom
fix/issues-5-6-7

Conversation

@cg8-5712

@cg8-5712 cg8-5712 commented Jul 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Implements the updated Issue #5/#6/#7 intake workflow and AMap Web Service support.

Closes #5
Closes #6
Closes #7

  • Add a versioned two-phase intake flow with phase gating, next-question guidance, and immutable answer revisions.
  • Add deterministic structured consistency checks with blocking/warning/info outcomes and human-safe actions.
  • Add optional server-side AMap route estimates using GCJ-02 coordinates, with explicit straight-line degradation when routing is unavailable.
  • Surface unresolved assessments and hypothesis-only direction candidates in the creator-only profile draft.
  • Reject formal confirmation transactionally when blocking assessments remain, while retaining idempotent confirmation behavior.
  • Add SQLite/PostgreSQL/MySQL migrations and broad automated coverage.
  • Add local .env plus process-environment support for the server-only AMap key, and an opt-in upstream route integration test.

Validation

  • cargo test --workspace --all-features --locked
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • Real AMap upstream test is opt-in and requires AMAP_WEBSERVICE_KEY; it checks walking and driving routes without exposing the key or raw upstream payloads.

Security

  • AMAP_WEBSERVICE_KEY is server-side only and is never exposed to browser code, artifacts, logs, or audit metadata.
  • Default test and CI paths remain credential-free and do not contact AMap.

Summary by CodeRabbit

  • 新功能
    • 上线两阶段问询:支持查看档案草稿、阶段进度/缺失项展示、持续补充作答与人工确认创建正式案件;新增确认接口与草稿字段(含安全访问控制)。
    • 引入地图路线估算参与摄入评估:地图不可用时自动降级到替代估算结果;记录评估与冲突原因。
    • 支持答案提交的替换与修订留痕,并在确认后写入审计。
  • 文档
    • 更新接口、数据库语义与开放接口说明;补充 AI 提示模板的管理与安全规范,并新增高德集成测试说明。
  • 测试
    • 扩展/新增草稿访问、确认流程、冲突处理与修订回归测试;补充迁移回滚安全性用例与 SQLite 外键恢复覆盖。

cg8-5712 added 6 commits July 25, 2026 11:09
Complete the intake flow through profile review and formal case creation.

- add creator-only profile draft retrieval with explicit draft, source, timestamp, and human-confirmation markers
- add idempotent confirmation that atomically creates the active case, elder profile, family membership, audit event, and session link
- reuse case record creation and convert concurrent session-link conflicts to 409 responses
- add confirmation attribution to intake sessions and cross-dialect migrations
- add versioned ai_prompt_templates storage for future approved prompt publication; keep the current intake path deterministic and rule-based
- document the contracts, authorization, transaction boundary, prompt governance, and OpenAPI schemas
- cover draft privacy, human overrides, idempotency, validation, and no-partial-case behavior with API contract tests

Validated with cargo fmt --all -- --check, cargo test --workspace --all-features --locked, and cargo clippy --workspace --all-targets --all-features --locked -- -D warnings.
Implement the updated Issue #5/#6/#7 intake workflow. Add versioned two-phase questions, immutable answer revisions, structured consistency assessments, profile direction hypotheses, and confirmation blocking for unresolved blocking checks. Integrate the optional server-side AMap Web Service route client with GCJ-02 validation and explicit straight-line degradation when no key or route is available. Add database migrations and automated coverage for phase gating, revisions, assessments, degraded routing, and transactional confirmation.
Add an environment-gated AMap Web Service integration check for public GCJ-02 walking and driving routes. The test skips when AMAP_WEBSERVICE_KEY is absent, never logs credentials or upstream payloads, and documents use of a restricted non-production key so normal CI remains offline and quota-free.
Load the ignored local .env file without overriding process environment variables, so server configuration and explicit AMap upstream integration tests accept either deployment-style environment configuration or local development configuration. Mark the live test ignored by default and make a missing or empty key fail when the test is explicitly invoked, preventing a skipped test from being reported as a successful upstream check.
Copilot AI review requested due to automatic review settings July 25, 2026 09:17

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

新增两阶段 intake 流程,支持结构化答案评估、AMap 路线估算、画像草稿读取,以及人工确认后原子创建案件、老人档案、成员关系和审计事件。

Changes

Intake confirmation flow

Layer / File(s) Summary
AMap runtime integration
.env.example, Cargo.toml, src/amap_service.rs, src/config.rs, .github/workflows/*, deploy/preview/*
新增 AMap 配置、路线估算服务、超时校验、应用状态注入及预览环境密钥传递。
Intake contracts and persistence
src/models.rs, src/entities/*, migration/src/*, migration/sql/*
新增阶段进度、评估、草稿、确认响应、答案修订和提示模板模型,并注册 SQLite、PostgreSQL、MySQL 迁移及安全回滚。
Assessment and intake service flow
src/intake_assessment.rs, src/services/intake_session_service.rs, src/services/case_service.rs
答题流程支持阶段校验、答案替换、修订记录、结构化评估、阻塞原因、草稿生成及确认时的案件事务。
Intake API exposure
src/routes/intake_sessions.rs, docs/openapi.yaml, docs/API.md, docs/DATABASE.md, docs/AI_GATEWAY.md
新增画像草稿与确认端点,并补充 OpenAPI schemas、状态流转、确认约束、幂等语义和提示模板说明。
Validation and deployment support
tests/api/*, tests/support/mod.rs, migration/src/lib.rs, docs/AMAP_INTEGRATION_TESTS.md
测试覆盖阶段切换、答案替换、草稿访问控制、阻塞评估、确认幂等性、审计事件、迁移回滚和 AMap opt-in 集成测试。

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant 家属
  participant IntakeSessionService
  participant AmapService
  participant CaseService
  家属->>IntakeSessionService: 提交分阶段答案
  IntakeSessionService->>AmapService: 请求步行路线估算
  AmapService-->>IntakeSessionService: 返回路线或降级结果
  IntakeSessionService-->>家属: 返回评估、阶段进度和下一问
  家属->>IntakeSessionService: 提交人工确认
  IntakeSessionService->>CaseService: 创建案件、老人档案和成员关系
  CaseService-->>IntakeSessionService: 返回案件结果
Loading

Possibly related PRs

  • AG-Angui/Angui#112 — 重构 intake 答案提交处理并引入阶段、评估、修订和结构化答案持久化。
  • AG-Angui/Angui#115 — 更新至相同的 AMap Web Service skill 子模块提交。
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 新增 .codex 技能子模块和 .gitmodules 变更与本次问询/AMap 功能无直接关系,属于额外范围。 如非必须,请移除该技能子模块及相关 gitmodules 变更;若保留,需说明其与本次功能的直接依赖。
Docstring Coverage ⚠️ Warning Docstring coverage is 26.14% 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 标题简洁且准确概括了两阶段 intake 与 AMap 路线支持的主要变更。
Linked Issues check ✅ Passed 实现覆盖了两阶段问询、草稿/确认、确定性评估、AMap 降级和幂等确认,符合 #5/#6/#7 的核心要求。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issues-5-6-7

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

@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: 19

🧹 Nitpick comments (11)
tests/api/intake_session_answers_post.rs (1)

274-297: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

建议校验修订记录的具体字段,而非仅校验数量。

当前只断言 intake_answer_revisions 计数为 2,无法区分“正确写入了一条 corrected 修订”与“误写了重复的 submitted 修订”等潜在缺陷。建议额外查询最新一条修订记录,断言 revision_kind == "corrected" 且 raw_answer 为新提交的内容。

As per path instructions, tests/** changes should have meaningful positive/negative/boundary coverage for changed behavior (immutable answer revisions is new behavior here).

🤖 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/intake_session_answers_post.rs` around lines 274 - 297,
增强修订记录断言:在现有 health_status 修订数量校验后,查询该会话该字段最新的 intake_answer_revisions::Entity
记录,并断言 revision_kind 为 "corrected" 且 raw_answer 等于 "Corrected fictional health
detail";保留现有数量断言。

Source: Path instructions

tests/api/intake_session_confirm_post.rs (1)

157-230: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

建议补充“二次确认携带不同 payload”的幂等性边界场景。

当前幂等性测试的两次请求使用完全相同的 confirmation_json(),只验证了重复提交同一数据的场景。建议再补充一次使用不同 profile 内容的二次确认请求,断言仍返回既有 case_id 且 case 详情未被新 payload 覆盖,以更严格地验证“幂等确认”契约(而非仅是重复提交同数据)。

As per path instructions, tests/** should verify meaningful boundary-case coverage for changed behavior, and idempotent confirmation is an explicit PR objective.

🤖 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/intake_session_confirm_post.rs` around lines 157 - 230, The test
post_confirm_creates_one_active_case_from_human_confirmed_overrides currently
retries with the same confirmation_json payload; change the second confirmation
request to use a distinct profile payload while preserving the existing
assertions that it returns the original case_id and only one case exists. Keep
or strengthen the detail assertion to verify the original health_notes remains
unchanged, proving the idempotent confirmation does not overwrite the case with
the second payload.

Source: Path instructions

src/services/intake_session_service.rs (1)

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

submit_intake_answer 包装器会静默禁用路线评估。

该包装器固定构造 AmapService::disabled(),任何仍调用旧签名的调用方都会得到 route_service_degraded 降级评估而非真实路线结果。目前测试固件(tests/api/intake_session_confirm_post.rs)走的正是这条路径,属于有意为之,但建议加文档注释说明“仅用于测试/无地图部署”,或改为接收 Option<&AmapService>,避免生产代码误用后静默降级。

🤖 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/services/intake_session_service.rs` around lines 90 - 107, 为
submit_intake_answer 包装器补充明确文档注释,说明其固定使用
AmapService::disabled(),仅适用于测试或无地图部署;保留现有调用行为,并提示生产调用方应使用
submit_intake_answer_with_map 以获得真实路线评估。
src/intake_assessment.rs (2)

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

建议补一条“可达性冲突为 blocking”的正向用例。

现有两个测试覆盖了降级 warning 与受控值 blocking,但 walking_reachability_conflict 与 time_order_impossible 这两条真正阻断确认的分支没有测试。后者可用纯本地数据(无需 AMap)直接构造。

🤖 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/intake_assessment.rs` around lines 311 - 374, 在 tests
模块中补充覆盖真正阻断分支的正向测试:使用本地构造的 IntakeStructuredFacts 验证
walking_reachability_conflict 和 time_order_impossible 分别产生 severity 为 blocking
的结果;测试应通过 AmapService::disabled() 运行,不依赖外部地图服务,并保留现有降级 warning 与受控值 blocking
测试不变。

60-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

直线降级的步速常数建议提取为具名常量并补充覆盖。

Line 86 的 1.2(m/s)与 Line 89 的截断转换是决定 walking_reachability_conflict 是否成立的核心参数,埋在表达式里不便审阅与调整。建议提取 const WALKING_METERS_PER_SECOND: f64 = 1.2; 并加注释说明该值只用于显式降级估算。

🤖 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/intake_assessment.rs` around lines 60 - 101, 在直线降级估算所在的 intake
路由逻辑中提取具名常量 WALKING_METERS_PER_SECOND,设置为 1.2,并注明该值仅用于显式降级估算;更新 fallback_seconds
的计算以复用该常量,并为该降级计算及其截断转换补充覆盖测试。
src/amap_service.rs (1)

103-120: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

HTTP 错误状态与传输层错误被合并为同一原因,降低可观测性。

Ok(_) if !is_success() 与 Err(_)(DNS/连接失败/超时等)都归为 TransportFailure。前者往往是密钥无效、配额超限等可恢复的配置问题,后者是真正的网络问题;混在一起会让排障时误判为网络故障。

♻️ 建议区分响应错误与传输错误
     let response = match self
             .client
             .get(format!("{}{path}", self.base_url))
             .query(&[
                 ("key", key.as_str()),
                 ("origin", origin_value.as_str()),
                 ("destination", destination_value.as_str()),
             ])
             .send()
             .await
         {
             Ok(response) if response.status().is_success() => response,
-            Ok(_) | Err(_) => {
+            Ok(_) => {
+                return RouteEstimate::Unavailable {
+                    reason: RouteUnavailableReason::BusinessFailure,
+                };
+            }
+            Err(_) => {
                 return RouteEstimate::Unavailable {
                     reason: RouteUnavailableReason::TransportFailure,
                 };
             }
         };
🤖 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/amap_service.rs` around lines 103 - 120, Update the response handling in
the route request flow to distinguish non-success HTTP responses from request
transport errors. Keep Err(_) mapped to
RouteUnavailableReason::TransportFailure, but map Ok(response) with a
non-success status to the appropriate separate HTTP or service error reason
available in RouteUnavailableReason, preserving successful responses as-is.
src/models.rs (1)

162-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

两个响应构造函数对 phase 的计算方式不一致。

IntakeSessionResponse::new 内部调用 IntakePhaseProgress::for_answers,而 SubmitIntakeAnswerResponse::new 却要求调用方预先算好 phase 再传入。二者服务于同一概念,建议统一为其中一种模式(推荐都在构造函数内部计算,减少调用方出错的可能性,例如遗漏调用 for_answers 或传入过期的 phase)。

Also applies to: 283-319

🤖 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/models.rs` around lines 162 - 186, 统一 IntakeSessionResponse::new 与
SubmitIntakeAnswerResponse::new 的 phase 计算方式:让 SubmitIntakeAnswerResponse::new
在内部基于其答案数据调用 IntakePhaseProgress::for_answers,并移除调用方传入的预计算 phase
参数及相关依赖;保持两个响应构造函数输出相同的 phase 进度字段,更新所有调用点以匹配新的签名。
migration/src/m0015_create_intake_prompt_templates.rs (1)

7-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

迁移名称与模块/文件命名不一致。

模块名为 m0015_create_intake_prompt_templates(文件、SQL 路径同名),但 MigrationName::name() 返回的是 "m0015_create_ai_prompt_templates"。虽然这与实际表名 ai_prompt_templates(见 src/entities/ai_prompt_templates.rs)一致,但与该迁移自身的模块/文件名不一致,容易在排查迁移历史表(seaql_migrations)时造成混淆。

♻️ 建议统一命名
 impl MigrationName for Migration {
     fn name(&self) -> &str {
-        "m0015_create_ai_prompt_templates"
+        "m0015_create_intake_prompt_templates"
     }
 }

或者反过来统一重命名模块/文件为 ai_prompt_templates,二者选其一即可。

🤖 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 `@migration/src/m0015_create_intake_prompt_templates.rs` around lines 7 - 11,
统一迁移标识命名:更新 MigrationName::name 中的名称,使其与迁移模块/文件及 SQL 路径
m0015_create_intake_prompt_templates 一致;或者将相关模块、文件和路径统一重命名为
ai_prompt_templates,但必须确保 MigrationName::name 与迁移实际标识保持一致。
src/entities/ai_prompt_templates.rs (1)

6-20: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Model 派生 Serialize,需留意未来是否会被直接暴露给 API。

注释说明这是"Versioned, approved system instructions",未来管理端点可能会发布/读取它们。当前没有路由直接序列化该实体,但一旦有人不小心在某个 handler 中直接 HttpResponse::Ok().json(model),system_instruction(内部提示词设计)会被泄露给客户端。建议未来暴露该数据时使用专门的 DTO 做字段白名单,而不是直接复用带 Serialize 的实体。

🤖 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/entities/ai_prompt_templates.rs` around lines 6 - 20, Remove the
Serialize derive from the ai_prompt_templates Model entity and avoid exposing it
directly through API responses. If this data is later returned by a handler,
introduce a dedicated DTO with an explicit field whitelist instead of
serializing Model, especially excluding system_instruction unless explicitly
required.
src/entities/intake_answer_revisions.rs (1)

4-15: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

不要直接为包含原始回答的 ORM Model 开放 Serialize。

该模型包含 raw_answer、structured_json 和 created_by_user_id。一旦被 handler、日志或调试响应直接序列化,就可能绕过“仅创建者可见”的隐私边界。建议移除 ORM 模型上的 Serialize,需要对外返回时使用经过授权和脱敏的专用 DTO;同时确认当前调用方没有直接序列化该模型。

As per path instructions:src/** 需要重点检查认证、授权、隐私、API 兼容性及数据丢失风险。

🤖 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/entities/intake_answer_revisions.rs` around lines 4 - 15, Remove the
Serialize derive from the ORM Model in intake_answer_revisions, and inspect its
current callers to ensure none directly serialize or expose the model. For
external responses, route data through an authorized, privacy-filtered DTO that
preserves the creator-only visibility boundary.

Source: Path instructions

migration/sql/sqlite/up/0015_create_intake_prompt_templates.sql (1)

1-7: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

请约束每个用途只能存在一个 published 模板。

当前唯一约束是 (purpose, version),因此同一 purpose 可以同时拥有多个 published 版本。若读取端按 purpose/status 查询,结果可能多行且选择不确定;并发发布也可能留下多个当前模板。请增加数据库约束,或在发布事务中原子地将旧版本置为 retired,并确认所有数据库实现一致。

As per path instructions:迁移需要检查跨 PostgreSQL、MySQL、SQLite 的数据完整性与安全回滚行为。

🤖 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 `@migration/sql/sqlite/up/0015_create_intake_prompt_templates.sql` around lines
1 - 7, 更新 ai_prompt_templates 的发布约束,使每个 purpose 同时最多存在一个 published 模板;优先为
PostgreSQL、MySQL、SQLite 分别采用等效的唯一约束或发布事务方案,并确保并发发布原子地将旧版本置为
retired。检查已有数据后再添加约束,处理冲突并提供安全、可回滚的迁移,保持各数据库实现一致。

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/API.md`:
- Around line 177-182: Update the “Intake draft and confirmation” section to
document that profile-draft responses include assessments,
confirmation_blocked_reasons, and direction_hypotheses, and that confirm returns
409 when any consistency assessment has severity “blocking.” Keep the existing
confirmation and idempotency behavior unchanged.

In `@docs/DATABASE.md`:
- Around line 193-195: 更新 docs/DATABASE.md 的 intake session 说明:移除“创建接口只写入
collecting”的过时表述,说明必填答案齐备时 create_intake_session 可直接写入 ready_for_confirmation;补充
intake_answer_revisions 的不可变答案修订语义,以及
assessment_json、structured_answers_json、confirmed_by_user_id 和 confirmed_at
字段含义,使文档与现有服务及迁移保持一致。

In `@docs/openapi.yaml`:
- Around line 882-909: Update the OpenAPI schemas for the intake contract to
match the Rust response models: add suspicious_motive to
IntakeProfileDraftFields, define IntakeAssessment, IntakeRouteEstimate, and
IntakeDirectionHypothesis, and add assessments, confirmation_blocked_reasons,
and direction_hypotheses to IntakeProfileDraft. Also reconcile
IntakeInitialAnswers, SubmitIntakeAnswerRequest, and SubmitIntakeAnswerResponse
with their model fields, including suspicious_motive, replace, structured,
phase, and assessments, while preserving strict additionalProperties validation.

In `@migration/sql/mysql/down/0015_drop_intake_prompt_templates.sql`:
- Line 1: Update the down migration for ai_prompt_templates so rollback does not
unconditionally drop the table or delete post-migration template data. Preserve
non-seed records by removing only migration-owned seed data, or abort the
rollback when non-seed data exists and require explicit backup confirmation;
also mark the destructive/data-loss behavior in the migration.

In `@migration/sql/mysql/down/0016_add_intake_assessments.sql`:
- Around line 1-5: Update the down migration to protect production data before
removing intake_answer_revisions, assessment_json, or structured_answers_json:
detect non-empty revision data and abort unless an explicit backup or migration
has completed, and ensure the deployment flow does not permit an unprotected
rollback. Mark this migration as a destructive change while preserving safe
behavior when the table is empty.

In `@migration/sql/mysql/down/0017_add_two_phase_intake_questions.sql`:
- Around line 1-3: 更新该迁移的回滚逻辑,避免在未保存原始状态时无条件激活全部 version 1 记录,并避免删除迁移后新增或人工修改的
version 2 数据;仅恢复迁移实际停用的题目状态,并仅删除迁移创建的固定题目 ID。若无法可靠保存并恢复这些状态,应将该迁移明确标记为不可逆。

In `@migration/sql/mysql/up/0017_add_two_phase_intake_questions.sql`:
- Line 1: 将迁移中的批量 UPDATE 限定为本次被替换题集的旧版本记录,不要停用所有 status 为 active 的问题;同时补充对本次实际修改
ID 及原始状态的可追踪记录,使 down 迁移仅恢复这些记录并还原其原始状态。确保 up/down 使用目标数据库支持的安全 SQL
语法,并保持其他版本及自定义 active 问题不变。

In `@migration/sql/postgres/down/0014_confirm_intake_sessions.sql`:
- Around line 1-3: The down migration for intake_sessions silently drops the
confirmed_at and confirmed_by_user_id columns without checking for existing
data, risking permanent data loss. Add a protective check before the two ALTER
TABLE DROP COLUMN statements that queries intake_sessions for any rows with
non-null values in either confirmed_at or confirmed_by_user_id and raises an
exception to halt the migration if such data is found, ensuring data integrity
before any irreversible deletion occurs.

In `@migration/sql/postgres/down/0015_drop_intake_prompt_templates.sql`:
- Line 1: Update the rollback migration containing DROP TABLE
ai_prompt_templates so it does not unconditionally delete prompt-template data:
reject the rollback when the table is non-empty, or require an explicit
archive/backup before removal. Also mark the rollback operation as destructive
according to the migration conventions.

In `@migration/sql/postgres/down/0016_add_intake_assessments.sql`:
- Around line 1-5: Update the down migration to prevent unprotected destructive
rollback: before dropping intake_answer_revisions or removing assessment_json
and structured_answers_json from intake_sessions, detect whether any data exists
and abort with a clear error, or perform an explicitly defined backup/migration.
Preserve all data unless the safety check confirms it is safe to proceed, and
mark the destructive rollback behavior as required by migration policy.

In `@migration/sql/postgres/down/0017_add_two_phase_intake_questions.sql`:
- Around line 1-3: 将回滚逻辑限定为本次迁移实际新增或修改的明确题目 ID,不要使用 version = 2 或 version = 1
的宽泛条件;删除仅本次迁移新增的记录,并恢复本次迁移修改记录各自保存的原始 status(包括 disabled),确保其他版本-2题目及原有状态不受影响。

In `@migration/sql/postgres/up/0014_confirm_intake_sessions.sql`:
- Line 1: Update the intake_sessions migration to add the confirmed_by_user_id
foreign key as NOT VALID with the constraint name
fk_intake_sessions_confirmed_by, deferring validation to a separate low-traffic
transaction using VALIDATE CONSTRAINT.

In `@migration/sql/sqlite/down/0014_confirm_intake_sessions.sql`:
- Around line 1-18: 为回滚脚本增加统一的失败清理路径,确保 CREATE、INSERT、DROP、ALTER 或 CREATE INDEX
任一步失败时都恢复 PRAGMA foreign_keys = ON。保留成功路径的恢复逻辑,并让 cleanup
可安全重复执行,避免连接在异常后继续保持外键校验关闭。

In `@migration/sql/sqlite/down/0015_drop_intake_prompt_templates.sql`:
- Line 1: Update the rollback migration around DROP TABLE ai_prompt_templates so
it does not unconditionally delete the prompt-template table or silently discard
published and historical data. Make the non-empty-table case fail or require
explicit archival/backup before removal, and mark this rollback as a destructive
operation.

In `@migration/sql/sqlite/down/0016_add_intake_assessments.sql`:
- Around line 1-16: Update the down migration around intake_answer_revisions and
intake_sessions_before_assessment to prevent unprotected data loss: check
whether revision rows or data in the newer intake_sessions columns exist, and
abort the rollback when they do, or perform an explicitly defined
backup/migration first. Do not drop intake_answer_revisions or rebuild
intake_sessions until the rollback is confirmed safe, and mark the destructive
behavior according to the migration policy.

In `@migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql`:
- Around line 1-3: 调整下线迁移中针对 version=2 的删除逻辑:执行删除前检查是否存在
intake_sessions.question_set_version = 2
的会话;若存在则拒绝迁移并保留题目定义,避免静默造成数据丢失。无引用会话时继续完成删除并恢复 version=1 的 active
状态,同时明确该回滚仅适用于未产生 version=2 会话的即时部署回滚。

In `@migration/sql/sqlite/up/0017_add_two_phase_intake_questions.sql`:
- Line 1: 将迁移中的全表状态更新限定为需要被版本 2 替换的旧题目,修改该 UPDATE 的 WHERE
条件以匹配明确的版本标识或题目集合,同时保留将这些题目的 status 更新为 disabled;不要影响其他版本或人工配置的 active 题目。

In `@src/intake_assessment.rs`:
- Around line 21-59: Move the time order validation block that checks if
available_seconds is less than 0 and pushes the "time_order_impossible" blocking
assessment to execute immediately after the time values last_seen_at and
follow_up_at are available, before any location-dependent validations. This
ensures the time order check is independent of location validation and blocking
errors are not skipped by early location-related returns. Keep all
location-validation early returns in their current positions after the relocated
time check.

In `@src/services/intake_session_service.rs`:
- Around line 334-364: 在确认流程读取 intake session 的查询中加入排他行锁,确保从 session 校验到创建并绑定
case 的整个事务串行执行。更新包含 `intake_sessions::Entity::find_by_id(session_id)` 的查询链以使用
`lock_exclusive()`,并保持现有 `transaction`、状态校验、case 创建及 `case_id` 回写逻辑不变。

---

Nitpick comments:
In `@migration/sql/sqlite/up/0015_create_intake_prompt_templates.sql`:
- Around line 1-7: 更新 ai_prompt_templates 的发布约束,使每个 purpose 同时最多存在一个 published
模板;优先为 PostgreSQL、MySQL、SQLite 分别采用等效的唯一约束或发布事务方案,并确保并发发布原子地将旧版本置为
retired。检查已有数据后再添加约束,处理冲突并提供安全、可回滚的迁移,保持各数据库实现一致。

In `@migration/src/m0015_create_intake_prompt_templates.rs`:
- Around line 7-11: 统一迁移标识命名:更新 MigrationName::name 中的名称,使其与迁移模块/文件及 SQL 路径
m0015_create_intake_prompt_templates 一致;或者将相关模块、文件和路径统一重命名为
ai_prompt_templates,但必须确保 MigrationName::name 与迁移实际标识保持一致。

In `@src/amap_service.rs`:
- Around line 103-120: Update the response handling in the route request flow to
distinguish non-success HTTP responses from request transport errors. Keep
Err(_) mapped to RouteUnavailableReason::TransportFailure, but map Ok(response)
with a non-success status to the appropriate separate HTTP or service error
reason available in RouteUnavailableReason, preserving successful responses
as-is.

In `@src/entities/ai_prompt_templates.rs`:
- Around line 6-20: Remove the Serialize derive from the ai_prompt_templates
Model entity and avoid exposing it directly through API responses. If this data
is later returned by a handler, introduce a dedicated DTO with an explicit field
whitelist instead of serializing Model, especially excluding system_instruction
unless explicitly required.

In `@src/entities/intake_answer_revisions.rs`:
- Around line 4-15: Remove the Serialize derive from the ORM Model in
intake_answer_revisions, and inspect its current callers to ensure none directly
serialize or expose the model. For external responses, route data through an
authorized, privacy-filtered DTO that preserves the creator-only visibility
boundary.

In `@src/intake_assessment.rs`:
- Around line 311-374: 在 tests 模块中补充覆盖真正阻断分支的正向测试:使用本地构造的 IntakeStructuredFacts
验证 walking_reachability_conflict 和 time_order_impossible 分别产生 severity 为
blocking 的结果;测试应通过 AmapService::disabled() 运行,不依赖外部地图服务,并保留现有降级 warning 与受控值
blocking 测试不变。
- Around line 60-101: 在直线降级估算所在的 intake 路由逻辑中提取具名常量
WALKING_METERS_PER_SECOND,设置为 1.2,并注明该值仅用于显式降级估算;更新 fallback_seconds
的计算以复用该常量,并为该降级计算及其截断转换补充覆盖测试。

In `@src/models.rs`:
- Around line 162-186: 统一 IntakeSessionResponse::new 与
SubmitIntakeAnswerResponse::new 的 phase 计算方式:让 SubmitIntakeAnswerResponse::new
在内部基于其答案数据调用 IntakePhaseProgress::for_answers,并移除调用方传入的预计算 phase
参数及相关依赖;保持两个响应构造函数输出相同的 phase 进度字段,更新所有调用点以匹配新的签名。

In `@src/services/intake_session_service.rs`:
- Around line 90-107: 为 submit_intake_answer 包装器补充明确文档注释,说明其固定使用
AmapService::disabled(),仅适用于测试或无地图部署;保留现有调用行为,并提示生产调用方应使用
submit_intake_answer_with_map 以获得真实路线评估。

In `@tests/api/intake_session_answers_post.rs`:
- Around line 274-297: 增强修订记录断言:在现有 health_status 修订数量校验后,查询该会话该字段最新的
intake_answer_revisions::Entity 记录,并断言 revision_kind 为 "corrected" 且 raw_answer
等于 "Corrected fictional health detail";保留现有数量断言。

In `@tests/api/intake_session_confirm_post.rs`:
- Around line 157-230: The test
post_confirm_creates_one_active_case_from_human_confirmed_overrides currently
retries with the same confirmation_json payload; change the second confirmation
request to use a distinct profile payload while preserving the existing
assertions that it returns the original case_id and only one case exists. Keep
or strengthen the detail assertion to verify the original health_notes remains
unchanged, proving the idempotent confirmation does not overwrite the case with
the second payload.
🪄 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: 7b405d42-6126-4e43-abe1-0fd651be131f

📥 Commits

Reviewing files that changed from the base of the PR and between d681779 and 6b00b0a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (58)
  • .codex/skills/Amap_webservice_api_skill
  • .env.example
  • .gitmodules
  • Cargo.toml
  • docs/AI_GATEWAY.md
  • docs/AMAP_INTEGRATION_TESTS.md
  • docs/API.md
  • docs/DATABASE.md
  • docs/openapi.yaml
  • migration/sql/mysql/down/0014_confirm_intake_sessions.sql
  • migration/sql/mysql/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/mysql/down/0016_add_intake_assessments.sql
  • migration/sql/mysql/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/mysql/up/0014_confirm_intake_sessions.sql
  • migration/sql/mysql/up/0015_create_intake_prompt_templates.sql
  • migration/sql/mysql/up/0016_add_intake_assessments.sql
  • migration/sql/mysql/up/0017_add_two_phase_intake_questions.sql
  • migration/sql/postgres/down/0014_confirm_intake_sessions.sql
  • migration/sql/postgres/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/postgres/down/0016_add_intake_assessments.sql
  • migration/sql/postgres/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/postgres/up/0014_confirm_intake_sessions.sql
  • migration/sql/postgres/up/0015_create_intake_prompt_templates.sql
  • migration/sql/postgres/up/0016_add_intake_assessments.sql
  • migration/sql/postgres/up/0017_add_two_phase_intake_questions.sql
  • migration/sql/sqlite/down/0014_confirm_intake_sessions.sql
  • migration/sql/sqlite/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/sqlite/down/0016_add_intake_assessments.sql
  • migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/sqlite/up/0014_confirm_intake_sessions.sql
  • migration/sql/sqlite/up/0015_create_intake_prompt_templates.sql
  • migration/sql/sqlite/up/0016_add_intake_assessments.sql
  • migration/sql/sqlite/up/0017_add_two_phase_intake_questions.sql
  • migration/src/lib.rs
  • migration/src/m0014_confirm_intake_sessions.rs
  • migration/src/m0015_create_intake_prompt_templates.rs
  • migration/src/m0016_add_intake_assessments.rs
  • migration/src/m0017_add_two_phase_intake_questions.rs
  • src/amap_service.rs
  • src/app_state.rs
  • src/config.rs
  • src/entities/ai_prompt_templates.rs
  • src/entities/intake_answer_revisions.rs
  • src/entities/intake_sessions.rs
  • src/entities/mod.rs
  • src/intake_assessment.rs
  • src/lib.rs
  • src/main.rs
  • src/models.rs
  • src/routes/intake_sessions.rs
  • src/services/case_service.rs
  • src/services/intake_session_service.rs
  • tests/api/intake_session_answers_post.rs
  • tests/api/intake_session_confirm_post.rs
  • tests/api/intake_session_profile_draft_get.rs
  • tests/api/intake_sessions_create_post.rs
  • tests/api/mod.rs
  • tests/support/mod.rs

Comment thread docs/API.md
Comment on lines +177 to +182
## Intake draft and confirmation

`GET /api/intake-sessions/{session_id}/profile-draft` returns the session creator's standardized profile draft. It is explicitly marked `draft`, lists its family-provided source scope and missing fields, includes a generated timestamp, and sets `requires_human_confirmation: true`. The endpoint does not infer a certain destination or expose the draft to commanders, volunteers, or other families before formal confirmation.

`POST /api/intake-sessions/{session_id}/confirm` is available only to that session creator after required answers are complete. The request must set `human_confirmed: true` and contains the family-reviewed profile, so corrected values supersede any draft value. One database transaction creates the active case, elder profile, creator's `family` membership, `case.created` audit event, and confirmed-session link. A repeat submission returns the already-created case instead of creating a duplicate. See `docs/openapi.yaml` for the exact schemas.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

确认接口的阻断语义未写入文档。

confirm_intake_session 在存在 severity == "blocking" 的一致性评估时返回 409,profile-draft 也会返回 assessments、confirmation_blocked_reasons 与 direction_hypotheses(见 src/services/intake_session_service.rs 中 get_intake_profile_draft/confirm_intake_session)。当前这两段说明都没提到这些字段与阻断条件,前端无法据此实现提示。建议补充一句说明。

🧰 Tools
🪛 LanguageTool

[uncategorized] ~181-~181: Forse volevi dire "ha duplicate"?
Context: ...lready-created case instead of creating a duplicate. See docs/openapi.yaml for the exact ...

(ER_01_001)

🤖 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 `@docs/API.md` around lines 177 - 182, Update the “Intake draft and
confirmation” section to document that profile-draft responses include
assessments, confirmation_blocked_reasons, and direction_hypotheses, and that
confirm returns 409 when any consistency assessment has severity “blocking.”
Keep the existing confirmation and idempotency behavior unchanged.

Comment thread docs/DATABASE.md
Comment on lines +193 to +195
The current creation API writes only `collecting`. The answer API advances a session with complete required answers to `ready_for_confirmation`; the confirmation API atomically creates the case, elder profile, family membership, and `case.created` audit event before setting `case_id`, `confirmed_by_user_id`, `confirmed_at`, and `status=confirmed`. Sensitive raw answers remain in the session record and are deliberately excluded from audit metadata and ordinary logs. The database limit is intersected with the server-only `ANGUI_INTAKE_ANSWER_HARD_MAX` safety cap; database configuration cannot increase that hard limit.

`ai_prompt_templates` is the platform-level, versioned store for approved AI system instructions. It records the purpose, immutable version, instruction body, lifecycle status, and creation/publication attribution. `purpose` is constrained to the currently defined business purposes rather than having separate prompt tables per module. Runtime business records persist only the selected template version; they never persist a raw prompt in audit metadata. The table is seeded with the `intake_next_question` template for future model-backed guidance, while the current intake endpoint remains rule-based. A later admin configuration API must create, publish, and retire versions with audit events; an intake request can never supply its own prompt.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

“创建接口只写入 collecting” 已与代码不符。

create_intake_session 现在在必填答案齐备时直接写入 ready_for_confirmation(src/services/intake_session_service.rs Line 52-56,tests/api/intake_sessions_create_post.rs 也断言创建响应为 ready_for_confirmation)。同时本段未提到新增的 intake_answer_revisions(不可变答案修订)以及 assessment_json、structured_answers_json、confirmed_by_user_id/confirmed_at 字段语义,建议一并补齐,否则表语义文档与 0014-0017 迁移脱节。

🤖 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 `@docs/DATABASE.md` around lines 193 - 195, 更新 docs/DATABASE.md 的 intake
session 说明:移除“创建接口只写入 collecting”的过时表述,说明必填答案齐备时 create_intake_session 可直接写入
ready_for_confirmation;补充 intake_answer_revisions 的不可变答案修订语义,以及
assessment_json、structured_answers_json、confirmed_by_user_id 和 confirmed_at
字段含义,使文档与现有服务及迁移保持一致。

Comment thread docs/openapi.yaml
Comment on lines +882 to +909
IntakeProfileDraft:
type: object
additionalProperties: false
required: [status, source_scope, generated_at, requires_human_confirmation, profile, missing_fields]
properties:
status: { type: string, const: draft }
source_scope: { type: string }
generated_at: { type: string, format: date-time }
requires_human_confirmation: { type: boolean, const: true }
profile: { $ref: "#/components/schemas/IntakeProfileDraftFields" }
missing_fields:
type: array
items: { type: string }

IntakeProfileDraftFields:
type: object
additionalProperties: false
required: [physical_description, clothing_description, health_notes, mobility_notes, transportation_ability, frequent_locations, last_seen_information, behavior_habits]
properties:
physical_description: { type: [string, "null"] }
clothing_description: { type: [string, "null"] }
health_notes: { type: [string, "null"] }
mobility_notes: { type: [string, "null"] }
transportation_ability: { type: [string, "null"] }
frequent_locations: { type: [string, "null"] }
last_seen_information: { type: [string, "null"] }
behavior_habits: { type: [string, "null"] }

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

IntakeProfileDraft schema 与实际响应不一致,会被严格校验拒绝。

get_intake_profile_draft 返回的 IntakeProfileDraft 还包含 assessments、confirmation_blocked_reasons、direction_hypotheses(见 src/services/intake_session_service.rs Line 297-317),而 IntakeProfileDraftFields 还包含 suspicious_motive(Line 311)。这里两个 schema 都声明了 additionalProperties: false 且未定义这些字段,契约测试/客户端生成器会直接判定响应非法。

📝 建议补齐字段
     IntakeProfileDraft:
       type: object
       additionalProperties: false
-      required: [status, source_scope, generated_at, requires_human_confirmation, profile, missing_fields]
+      required: [status, source_scope, generated_at, requires_human_confirmation, profile, missing_fields, assessments, confirmation_blocked_reasons, direction_hypotheses]
       properties:
         status: { type: string, const: draft }
         source_scope: { type: string }
         generated_at: { type: string, format: date-time }
         requires_human_confirmation: { type: boolean, const: true }
         profile: { $ref: "`#/components/schemas/IntakeProfileDraftFields`" }
         missing_fields:
           type: array
           items: { type: string }
+        assessments:
+          type: array
+          items: { $ref: "`#/components/schemas/IntakeAssessment`" }
+        confirmation_blocked_reasons:
+          type: array
+          items: { type: string }
+        direction_hypotheses:
+          type: array
+          items: { $ref: "`#/components/schemas/IntakeDirectionHypothesis`" }
 
     IntakeProfileDraftFields:
       type: object
       additionalProperties: false
-      required: [physical_description, clothing_description, health_notes, mobility_notes, transportation_ability, frequent_locations, last_seen_information, behavior_habits]
+      required: [physical_description, clothing_description, health_notes, mobility_notes, transportation_ability, frequent_locations, last_seen_information, behavior_habits, suspicious_motive]
       properties:
@@
         behavior_habits: { type: [string, "null"] }
+        suspicious_motive: { type: [string, "null"] }

需要同时新增 IntakeAssessment、IntakeRouteEstimate、IntakeDirectionHypothesis 三个 schema。顺带请核对本 PR 引入的其他契约漂移:IntakeInitialAnswers 缺 suspicious_motive、SubmitIntakeAnswerRequest 缺 replace/structured、SubmitIntakeAnswerResponse 缺 phase/assessments 等阶段字段。

#!/bin/bash
fd -t f 'models.rs' src -x rg -nH -A14 'struct (IntakeProfileDraft|IntakeProfileDraftFields|IntakeAssessment|IntakeDirectionHypothesis|IntakeRouteEstimate|ConfirmedIntakeProfile)\b' {}
rg -n 'suspicious_motive|IntakeAssessment|direction_hypotheses' docs/openapi.yaml
🤖 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 `@docs/openapi.yaml` around lines 882 - 909, Update the OpenAPI schemas for the
intake contract to match the Rust response models: add suspicious_motive to
IntakeProfileDraftFields, define IntakeAssessment, IntakeRouteEstimate, and
IntakeDirectionHypothesis, and add assessments, confirmation_blocked_reasons,
and direction_hypotheses to IntakeProfileDraft. Also reconcile
IntakeInitialAnswers, SubmitIntakeAnswerRequest, and SubmitIntakeAnswerResponse
with their model fields, including suspicious_motive, replace, structured,
phase, and assessments, while preserving strict additionalProperties validation.

@@ -0,0 +1 @@
DROP TABLE IF EXISTS ai_prompt_templates;

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

回滚会无条件删除所有提示模板数据。

DROP TABLE IF EXISTS 不仅删除迁移种子,也会删除后续创建的模板、发布历史及配置。请在回滚前保留数据、仅删除迁移拥有的记录,或在发现非种子数据时使回滚失败并要求显式备份确认。

As per path instructions:需要标记迁移中的破坏性变更及可能导致数据丢失的回滚行为。

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 1-1: Dropping a table may break existing clients.

(ban-drop-table)

🤖 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 `@migration/sql/mysql/down/0015_drop_intake_prompt_templates.sql` at line 1,
Update the down migration for ai_prompt_templates so rollback does not
unconditionally drop the table or delete post-migration template data. Preserve
non-seed records by removing only migration-owned seed data, or abort the
rollback when non-seed data exists and require explicit backup confirmation;
also mark the destructive/data-loss behavior in the migration.

Source: Path instructions

Comment on lines +1 to +5
DROP TABLE IF EXISTS intake_answer_revisions;
-- statement-break
ALTER TABLE intake_sessions DROP COLUMN assessment_json;
-- statement-break
ALTER TABLE intake_sessions DROP COLUMN structured_answers_json;

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

阻止无保护的生产回滚数据丢失。

该 down migration 会永久删除 intake_answer_revisions 全部修订记录,并丢弃 assessment_json 与 structured_answers_json。对于不可变答案历史和审计数据,这不是可逆操作;至少应在表非空时中止回滚,或先完成明确的数据备份/迁移,并在部署流程中禁止直接执行。

As per path instructions:migration/** 必须检查安全的回滚行为并标记破坏性变更。

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 1-1: Dropping a table may break existing clients.

(ban-drop-table)


[warning] 3-3: Dropping a column may break existing clients.

(ban-drop-column)


[warning] 5-5: Dropping a column may break existing clients.

(ban-drop-column)

🤖 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 `@migration/sql/mysql/down/0016_add_intake_assessments.sql` around lines 1 - 5,
Update the down migration to protect production data before removing
intake_answer_revisions, assessment_json, or structured_answers_json: detect
non-empty revision data and abort unless an explicit backup or migration has
completed, and ensure the deployment flow does not permit an unprotected
rollback. Mark this migration as a destructive change while preserving safe
behavior when the table is empty.

Sources: Path instructions, Linters/SAST tools

Comment on lines +1 to +16
DROP TABLE IF EXISTS intake_answer_revisions;
-- statement-break
PRAGMA foreign_keys = OFF;
-- statement-break
CREATE TABLE intake_sessions_before_assessment (
id TEXT PRIMARY KEY NOT NULL, created_by_user_id TEXT NOT NULL, case_id TEXT, question_set_version INTEGER NOT NULL,
status TEXT NOT NULL CHECK (status IN ('collecting', 'ready_for_confirmation', 'confirmed', 'closed')), answers_json TEXT NOT NULL,
confirmed_by_user_id TEXT REFERENCES users(id) ON DELETE RESTRICT, confirmed_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE RESTRICT, FOREIGN KEY (case_id) REFERENCES cases(id) ON DELETE SET NULL, UNIQUE (case_id)
);
-- statement-break
INSERT INTO intake_sessions_before_assessment (id, created_by_user_id, case_id, question_set_version, status, answers_json, confirmed_by_user_id, confirmed_at, created_at, updated_at) SELECT id, created_by_user_id, case_id, question_set_version, status, answers_json, confirmed_by_user_id, confirmed_at, created_at, updated_at FROM intake_sessions;
-- statement-break
DROP TABLE intake_sessions;
-- statement-break
ALTER TABLE intake_sessions_before_assessment RENAME TO intake_sessions;

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

阻止无保护的生产回滚数据丢失。

该 down migration 会删除 intake_answer_revisions 全部修订记录,并通过重建表丢弃两个新增字段中的数据。对于不可变答案历史和评估结果,回滚前应检查数据是否存在并中止,或先完成明确的数据备份/迁移。

As per path instructions:migration/** 必须检查安全的回滚行为并标记破坏性变更。

🤖 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 `@migration/sql/sqlite/down/0016_add_intake_assessments.sql` around lines 1 -
16, Update the down migration around intake_answer_revisions and
intake_sessions_before_assessment to prevent unprotected data loss: check
whether revision rows or data in the newer intake_sessions columns exist, and
abort the rollback when they do, or perform an explicitly defined
backup/migration first. Do not drop intake_answer_revisions or rebuild
intake_sessions until the rollback is confirmed safe, and mark the destructive
behavior according to the migration policy.

Sources: Path instructions, Linters/SAST tools

Comment on lines +1 to +3
DELETE FROM intake_question_definitions WHERE version = 2;
-- statement-break
UPDATE intake_question_definitions SET status = 'active' WHERE version = 1;

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

下线迁移中的 DELETE 会永久丢失 version=2 的题目定义,可能影响已存在的 intake 会话。

若在生产环境执行 up 之后已有 intake_sessions.question_set_version = 2 的会话产生,再执行本 down 迁移会把这些题目定义整批删除。后续这些会话再次提交答案时,questions_for_version 将查不到对应版本的题目,可能触发 ApiError::Internal(详见 src/services/intake_session_service.rs 中 active_questions/questions_for_version 依赖 version 行仍然存在)。请确认该 down 迁移仅用于部署失败时的即时回滚(此时不会有生产数据处于 version=2),而不是一个可在任意时间安全执行的回滚路径。

As per path instructions, migrations should be checked for "safe forward and rollback behavior" and to "flag destructive changes... and migrations that cannot be reapplied."

🤖 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 `@migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql` around
lines 1 - 3, 调整下线迁移中针对 version=2 的删除逻辑:执行删除前检查是否存在
intake_sessions.question_set_version = 2
的会话;若存在则拒绝迁移并保留题目定义,避免静默造成数据丢失。无引用会话时继续完成删除并恢复 version=1 的 active
状态,同时明确该回滚仅适用于未产生 version=2 会话的即时部署回滚。

Source: Path instructions

Comment thread migration/sql/sqlite/up/0017_add_two_phase_intake_questions.sql Outdated
Comment thread src/intake_assessment.rs Outdated
Comment thread src/services/intake_session_service.rs
cg8-5712 added 2 commits July 25, 2026 18:05
Pass AMAP_WEBSERVICE_KEY only to preview deployment runtime configuration and document the opt-in upstream verification receipt. Guard destructive intake migration rollbacks when production data exists, restore v1 question statuses from a migration-owned backup, and restrict v2 question changes to explicit seed IDs. Add SQLite migration safety regression coverage.
Add a named NOT VALID foreign key after the nullable confirmation column so deployment does not validate historical rows in the migration transaction. Keep new writes constrained, remove the named constraint explicitly on rollback, and assert the expected SQL shape in migration tests.

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
.github/workflows/preview.yml (1)

96-102: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

不要向未受信的预览产物注入 AMap 密钥。

此任务仅排除关联 PR 的 push,但仍允许任意可写分支的直接 push。部署时运行的是 head_sha 对应的后端产物;checkout 默认分支只保护脚本,不能保护该二进制。恶意后端可读取 AMAP_WEBSERVICE_KEY 并外传。

请将密钥注入限制到受保护、受信任的 ref,或让分支预览始终使用无密钥的降级路径。

As per path instructions: GitHub Actions 变更必须检查 untrusted pull-request safety 和 secret exposure。

🤖 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 @.github/workflows/preview.yml around lines 96 - 102, 限制
.github/workflows/preview.yml 中 AMAP_WEBSERVICE_KEY 的注入,仅在受保护且可信的 ref 上提供该
secret;对于任意可写分支或不受信任的预览,移除该环境变量并确保后端使用无密钥降级路径。保留 PREVIEW_* 路径及 deploy-local.sh
的现有部署流程不变。

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 `@migration/sql/mysql/down/0017_add_two_phase_intake_questions.sql`:
- Around line 1-2: 完善 m0017 回滚中的 ensure_rollback_is_safe 校验,在删除
intake_question_definitions 前比较所有迁移种子字段,包括 prompt、field_code、排序、校验参数及现有
version、status、时间戳;任一字段被运营方修改时都必须拒绝回滚。补充覆盖“内容字段被修改但 updated_at 未同步”的回滚拒绝测试,并确保
MySQL、PostgreSQL 和 SQLite 行为一致。

In `@migration/sql/postgres/down/0017_add_two_phase_intake_questions.sql`:
- Around line 4-8: 更新回滚迁移中的状态恢复逻辑,先校验备份集合对应的题目记录仍处于迁移后的预期状态
disabled;任何记录不满足时应拒绝回滚并避免执行 UPDATE。仅在校验通过后恢复 previous_status,不能只依赖 v2 数据是否存在。

In `@migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql`:
- Around line 4-8: 修改回滚脚本中的状态恢复 UPDATE,使其仅在题目仍处于迁移后预期状态时恢复
backup.previous_status;对已被人工修改、状态不匹配的记录拒绝回滚并避免覆盖。保留
intake_question_definition_status_backup 的 question_id 过滤,并确保 intake-q-0001 至
intake-q-0008 的状态变更不会被静默替换。

---

Outside diff comments:
In @.github/workflows/preview.yml:
- Around line 96-102: 限制 .github/workflows/preview.yml 中 AMAP_WEBSERVICE_KEY
的注入,仅在受保护且可信的 ref 上提供该 secret;对于任意可写分支或不受信任的预览,移除该环境变量并确保后端使用无密钥降级路径。保留
PREVIEW_* 路径及 deploy-local.sh 的现有部署流程不变。
🪄 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: 93aeacad-b5de-451a-be8e-29df738aed5e

📥 Commits

Reviewing files that changed from the base of the PR and between 6b00b0a and b66c8f0.

📒 Files selected for processing (28)
  • .github/WORKFLOWS.md
  • .github/workflows/pr-preview.yml
  • .github/workflows/preview.yml
  • .github/workflows/release.yml
  • deploy/preview/compose.local.yml
  • deploy/preview/deploy-local.sh
  • docs/AMAP_INTEGRATION_TESTS.md
  • migration/sql/mysql/down/0014_confirm_intake_sessions.sql
  • migration/sql/mysql/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/mysql/down/0016_add_intake_assessments.sql
  • migration/sql/mysql/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/mysql/up/0017_add_two_phase_intake_questions.sql
  • migration/sql/postgres/down/0014_confirm_intake_sessions.sql
  • migration/sql/postgres/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/postgres/down/0016_add_intake_assessments.sql
  • migration/sql/postgres/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/postgres/up/0017_add_two_phase_intake_questions.sql
  • migration/sql/sqlite/down/0014_confirm_intake_sessions.sql
  • migration/sql/sqlite/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/sqlite/down/0016_add_intake_assessments.sql
  • migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql
  • migration/sql/sqlite/up/0017_add_two_phase_intake_questions.sql
  • migration/src/lib.rs
  • migration/src/m0014_confirm_intake_sessions.rs
  • migration/src/m0015_create_intake_prompt_templates.rs
  • migration/src/m0016_add_intake_assessments.rs
  • migration/src/m0017_add_two_phase_intake_questions.rs
  • src/amap_service.rs
🚧 Files skipped from review as they are similar to previous changes (13)
  • migration/sql/postgres/down/0014_confirm_intake_sessions.sql
  • migration/sql/postgres/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/sqlite/down/0015_drop_intake_prompt_templates.sql
  • migration/sql/mysql/down/0015_drop_intake_prompt_templates.sql
  • migration/src/m0016_add_intake_assessments.rs
  • migration/sql/mysql/down/0016_add_intake_assessments.sql
  • migration/sql/sqlite/down/0014_confirm_intake_sessions.sql
  • migration/sql/postgres/down/0016_add_intake_assessments.sql
  • migration/src/m0015_create_intake_prompt_templates.rs
  • migration/sql/sqlite/down/0016_add_intake_assessments.sql
  • migration/src/m0014_confirm_intake_sessions.rs
  • migration/src/m0017_add_two_phase_intake_questions.rs
  • src/amap_service.rs

Comment on lines +1 to +2
-- Destructive rollback: m0017 refuses this script when v2 data has been used or modified.
DELETE FROM intake_question_definitions WHERE id IN ('intake-q-0201', 'intake-q-0202', 'intake-q-0203', 'intake-q-0204', 'intake-q-0205', 'intake-q-0206', 'intake-q-0207', 'intake-q-0208', 'intake-q-0209');

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

完整校验迁移拥有的 v2 题目内容后再删除。

m0017 的回滚保护目前只比较 version、status 和时间戳;若运营方修改了 prompt、field_code、排序或校验参数但未同步 updated_at,这里仍会删除该修改。请在 ensure_rollback_is_safe 查询中比较所有种子字段,并补充该场景的回滚拒绝测试。

As per path instructions, “Check SeaORM migrations for safe forward and rollback behavior across PostgreSQL, MySQL, and SQLite. Flag destructive changes.”

🤖 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 `@migration/sql/mysql/down/0017_add_two_phase_intake_questions.sql` around
lines 1 - 2, 完善 m0017 回滚中的 ensure_rollback_is_safe 校验,在删除
intake_question_definitions 前比较所有迁移种子字段,包括 prompt、field_code、排序、校验参数及现有
version、status、时间戳;任一字段被运营方修改时都必须拒绝回滚。补充覆盖“内容字段被修改但 updated_at 未同步”的回滚拒绝测试,并确保
MySQL、PostgreSQL 和 SQLite 行为一致。

Source: Path instructions

Comment on lines +4 to +8
UPDATE intake_question_definitions SET status = (
SELECT previous_status FROM intake_question_definition_status_backup backup
WHERE backup.question_id = intake_question_definitions.id
)
WHERE id IN (SELECT question_id FROM intake_question_definition_status_backup);

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

回滚前不要无条件覆盖迁移后的题目状态。

该 UPDATE 会把备份集合中的所有题目直接恢复为旧状态。若 intake-q-0001 至 intake-q-0008 在迁移后被人工修改、但尚未产生 v2 数据,这些修改会被静默丢失。请在回滚前校验这些记录仍处于迁移后的预期状态(disabled),否则拒绝回滚;不要仅依赖 v2 数据检查。

As per path instructions: migration 变更必须保证安全的 rollback behavior 并避免数据破坏。

🤖 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 `@migration/sql/postgres/down/0017_add_two_phase_intake_questions.sql` around
lines 4 - 8, 更新回滚迁移中的状态恢复逻辑,先校验备份集合对应的题目记录仍处于迁移后的预期状态
disabled;任何记录不满足时应拒绝回滚并避免执行 UPDATE。仅在校验通过后恢复 previous_status,不能只依赖 v2 数据是否存在。

Source: Path instructions

Comment on lines +4 to +8
UPDATE intake_question_definitions SET status = (
SELECT previous_status FROM intake_question_definition_status_backup backup
WHERE backup.question_id = intake_question_definitions.id
)
WHERE id IN (SELECT question_id FROM intake_question_definition_status_backup);

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

回滚前不要无条件覆盖迁移后的题目状态。

该 SQLite 回滚同样会直接恢复备份状态;迁移后对 intake-q-0001 至 intake-q-0008 的人工状态修改可能被静默覆盖。请在回滚前确认记录仍处于迁移后的预期状态,否则拒绝回滚。

As per path instructions: migration 变更必须保证安全的 rollback behavior 并避免数据破坏。

🤖 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 `@migration/sql/sqlite/down/0017_add_two_phase_intake_questions.sql` around
lines 4 - 8, 修改回滚脚本中的状态恢复 UPDATE,使其仅在题目仍处于迁移后预期状态时恢复
backup.previous_status;对已被人工修改、状态不匹配的记录拒绝回滚并避免覆盖。保留
intake_question_definition_status_backup 的 question_id 过滤,并确保 intake-q-0001 至
intake-q-0008 的状态变更不会被静默替换。

Source: Path instructions

cg8-5712 added 2 commits July 25, 2026 18:12
Evaluate follow-up time ordering immediately after both timestamps parse, before any location-dependent early return. This keeps confirmation blocked for impossible timelines even when locations are absent, invalid, or use an unverified coordinate system. Add a regression test for the missing-location case.
Lock the intake session row for the complete confirmation transaction so concurrent requests cannot create orphan cases. Refuse m0017 rollback unless every v2 seed matches all migration-owned fields and all backed-up v1 questions remain disabled; add regression coverage for prompt and status edits.

@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.

🧹 Nitpick comments (1)
migration/src/lib.rs (1)

604-611: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

不要用独立字符串断言替代 PostgreSQL 迁移执行测试。

contains("constraint fk_intake_sessions_confirmer") 与 contains("not valid") 可能分别命中不相关内容,无法证明 NOT VALID 修饰了目标外键,也无法验证迁移在 PostgreSQL 上可执行。当前文件的实际迁移执行测试仅覆盖 SQLite;请补充 PostgreSQL/MySQL 的 up/down/up 或回滚安全测试,至少将静态断言改为匹配完整约束语句。

As per path instructions:需检查 SeaORM 迁移在 PostgreSQL、MySQL 和 SQLite 上的安全前进与回滚行为。

🤖 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 `@migration/src/lib.rs` around lines 604 - 611, Replace the independent string
assertions in postgres_confirmation_foreign_key_defers_historical_validation
with a PostgreSQL migration execution test that verifies the up/down/up or
rollback-safe behavior of the migration. Extend the migration test coverage to
PostgreSQL, MySQL, and SQLite for safe forward and rollback execution, and if
retaining a static check, match the complete target foreign-key constraint
including its NOT VALID clause.

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.

Nitpick comments:
In `@migration/src/lib.rs`:
- Around line 604-611: Replace the independent string assertions in
postgres_confirmation_foreign_key_defers_historical_validation with a PostgreSQL
migration execution test that verifies the up/down/up or rollback-safe behavior
of the migration. Extend the migration test coverage to PostgreSQL, MySQL, and
SQLite for safe forward and rollback execution, and if retaining a static check,
match the complete target foreign-key constraint including its NOT VALID clause.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 26470454-dcd0-4f67-a027-55828265c499

📥 Commits

Reviewing files that changed from the base of the PR and between 54f3283 and a8e63d7.

📒 Files selected for processing (4)
  • migration/src/lib.rs
  • migration/src/m0017_add_two_phase_intake_questions.rs
  • src/intake_assessment.rs
  • src/services/intake_session_service.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • migration/src/m0017_add_two_phase_intake_questions.rs
  • src/intake_assessment.rs
  • src/services/intake_session_service.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants