diff --git a/coverage/ontology-coverage-audit.md b/coverage/ontology-coverage-audit.md index 0736c9b..0e44afc 100644 --- a/coverage/ontology-coverage-audit.md +++ b/coverage/ontology-coverage-audit.md @@ -3,7 +3,7 @@ > 本体条目 ↔ 文档章节 ↔ 代码路径 ↔ 测试用例 三方对齐审计。 > 证据强度:`anchored`(三处对齐)/ `documented`(仅文档)/ `implemented`(仅代码)/ `gap`(缺失)。 > -> **刷新时间**:2026-08-02(智能预约 M0 Batch 0 契约冻结后刷新) +> **刷新时间**:2026-08-02(智能预约 M0 Batch 2 会话与只读编排实现后刷新) ## 审计方法 @@ -25,9 +25,9 @@ | Relation | 112 | | **合计** | **313** | -证据强度:`anchored` 250(79.9%)/ `documented` 52 / `gap` 9 / `implemented` 2。 +证据强度:`anchored` 274(87.5%)/ `documented` 28 / `gap` 9 / `implemented` 2。 -> anchored 占比相对早期快照下降,是因为主动录入了 P1/P2 `gap`、客户端埋点和智能预约 Batch 0 `documented` 条目;端点漂移仍为 0。 +> 智能预约 Batch 2 的真实代码与测试已替换 24 条计划证据;P1/P2 `gap` 和未实现客户端埋点仍保留原证据强度,端点与 JPA 实体漂移均为 0。 校验: @@ -36,7 +36,7 @@ python3 docs/graph/validate_ontology.py docs python3 docs/graph/audit_drift.py ``` -当前结果:校验通过;HTTP 端点 68/68、JPA Entity 16/16 对齐;非 JPA/计划领域实体 14 个;代码与本体均无单边漂移。智能预约五个未实现端点以 `planned METHOD /path` 建模,不冒充当前代码端点。 +当前结果:校验通过;HTTP 端点 73/73、JPA Entity 17/17 对齐;非 JPA/计划领域实体 13 个;代码与本体均无单边漂移。智能预约五个端点及会话实体已由 Batch 2 真实代码和测试锚定。 ## 元数据卫生 @@ -72,7 +72,7 @@ python3 docs/graph/audit_drift.py | customer_timeline | ✅ | ✅ read model | StoreCustomerTimelineServiceTest | anchored | StoreCustomer + BusinessEvent 的同店低敏事实投影;jpa:false | | highlight_fail_reason | ✅ | ✅ 枚举 | ReportHighlightVideoServiceTest | anchored | jpa:false | | current_user | ✅ | ✅ record | SessionTokenServiceTest | anchored | jpa:false | -| booking_agent_session | ✅ | —(Batch 0 设计) | — | documented | M0 短期会话;实现批创建 JPA/迁移/测试后才可改 anchored | +| booking_agent_session | ✅ | ✅ | BookingAgentSessionMigrationTest / BookingAgentServiceTest | anchored | M0 短期会话;六态、owner/store、版本与 TTL,不关联 Appointment | | membership 等 5 个 | 方向库 | — | — | gap | roadmap 占位 | ## 规则覆盖度(55 条) @@ -149,14 +149,14 @@ python3 docs/graph/audit_drift.py 当前 validate 规模以脚本输出为准(313 条目)。 -## 智能预约 M0 Batch 0 状态(2026-08-02) +## 智能预约 M0 Batch 2 状态(2026-08-02) | 类型 | 已冻结 ID | 证据 | |------|------|------| -| Entity | `entity:booking_agent_session` | documented | -| Action | `create/submit/transcribe/handoff/cancel_booking_agent_*` 共 5 条 | documented | -| Event | `booking_agent_started/draft_ready/fallback` | documented | -| Rule | `BR-BA-001..005` | documented | -| Relation | owner/store/session 读写与三条 EMITS 共 10 条 | documented | +| Entity | `entity:booking_agent_session` | anchored | +| Action | `create/submit/transcribe/handoff/cancel_booking_agent_*` 共 5 条 | anchored | +| Event | `booking_agent_started/draft_ready/fallback` | anchored | +| Rule | `BR-BA-001..005` | anchored | +| Relation | owner/store/session 读写与三条 EMITS 共 10 条 | anchored | -契约评审已通过,尚未实现。下一实现批必须以真实 code/test 路径替换空证据,并将 `BookingAgentSession` 从计划对象切换为真实 JPA 实体;禁止仅修改 `evidence` 文字。 +Batch 2 已以真实 code/test 路径锚定会话、权限、状态/版本/TTL、音频边界、只读号源与低敏事件。Batch 3 前端仍未实现;真实供应商 smoke 与生产开启仍未授权。 diff --git a/graph/audit_drift.py b/graph/audit_drift.py index 50f4b0f..8f43e72 100755 --- a/graph/audit_drift.py +++ b/graph/audit_drift.py @@ -2,13 +2,13 @@ """ 宠小它 本体—代码漂移审计脚本。 -扫描 backend/src/main/java/com/petstore/controller/*.java,提取所有 HTTP 端点 +递归扫描 backend/src/main/java/com/petstore 下的 *Controller.java,提取所有 HTTP 端点 (类级 @RequestMapping + 方法级 @GetMapping/@PostMapping/@PutMapping/@DeleteMapping), 对比 docs/graph/ontology.jsonl 中 Action 的 entrypoints,报告: 1. code-only:代码里有但本体未录入的端点(drift:应补录本体或确认废弃) 2. ontology-only:本体里有但代码里找不到的端点(drift:可能是已删除接口或路径写错) -3. entity-coverage:扫描 backend/src/main/java/com/petstore/entity/*.java,对比 ontology.jsonl 的 Entity, +3. entity-coverage:递归扫描 backend/src/main/java/com/petstore 下的 @Entity 类,对比 ontology.jsonl 的 Entity, 报告未录入的实体类。 用法: @@ -28,21 +28,31 @@ VALID_TYPES = {"Entity", "Action"} def find_controller_files(repo_root): - ctrl_dir = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore", "controller") - if not os.path.isdir(ctrl_dir): + source_root = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore") + if not os.path.isdir(source_root): return [] - return sorted( - os.path.join(ctrl_dir, f) for f in os.listdir(ctrl_dir) if f.endswith(".java") - ) + files = [] + for directory, _, names in os.walk(source_root): + files.extend( + os.path.join(directory, name) + for name in names + if name.endswith("Controller.java") + ) + return sorted(files) def find_entity_files(repo_root): - ent_dir = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore", "entity") - if not os.path.isdir(ent_dir): + source_root = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore") + if not os.path.isdir(source_root): return [] - return sorted( - os.path.join(ent_dir, f) for f in os.listdir(ent_dir) if f.endswith(".java") - ) + files = [] + for directory, _, names in os.walk(source_root): + files.extend( + os.path.join(directory, name) + for name in names + if name.endswith(".java") + ) + return sorted(files) # 类级 @RequestMapping("/api/xxx") diff --git a/graph/ontology.jsonl b/graph/ontology.jsonl index bf4bb00..14106a1 100644 --- a/graph/ontology.jsonl +++ b/graph/ontology.jsonl @@ -140,7 +140,7 @@ {"id":"rule:BR-USER-002","type":"Rule","name":"用户更新仅本人","domain":"user","appliesTo":["action:update_user","action:get_user_info"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser; #info","evidence":"anchored"} {"id":"rule:BR-CONFIG-001","type":"Rule","name":"生产配置环境变量","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml; application-example.yml","doc":"backend/README.md 环境变量表","evidence":"anchored"} {"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile 强制只读 schema 安全配置","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java; backend/src/main/java/com/petstore/config/CorsConfig.java; backend/src/main/java/com/petstore/PetstoreApplication.java","doc":"backend/README.md production profile; docs/架构决策-全局CORS单一策略-2026-08-02.md; docs/架构决策-SessionToken严格过期边界-2026-08-02.md","test":"ProductionConfigurationValidatorTest; CorsBoundaryContractTest","evidence":"anchored","note":"生产禁用自动建表/show-sql/SMS 万能码/默认数据初始化,并拒绝弱密钥、越界 session TTL、占位域名和宽 CORS;全局 CorsFilter 是唯一策略,Controller 禁止局部 @CrossOrigin"} -{"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","test":"CorsBoundaryContractTest","evidence":"anchored","note":"备份和八迁移后运行 schema validate + 31 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读并验证 CORS allow/deny"} +{"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","test":"CorsBoundaryContractTest","evidence":"anchored","note":"备份并执行目标版本全部迁移后运行 schema validate + 31 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读并验证 CORS allow/deny"} {"id":"rel:user_owns_pets","type":"Relation","subject":"entity:user","predicate":"OWNS","object":"entity:pet","cardinality":"1—N","evidence":"anchored","doc":"Pet.owner_user_id → User.id","domain":"user"} {"id":"rel:pet_has_appointments","type":"Relation","subject":"entity:pet","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.pet_id(可空)","domain":"pet"} {"id":"rel:store_has_service_types","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:service_type","cardinality":"1—N","evidence":"anchored","doc":"ServiceType.store_id(NULL=系统默认)","domain":"store"} @@ -287,27 +287,27 @@ {"id":"rel:create_appointment_emits_rebook_created","type":"Relation","subject":"action:create_appointment","predicate":"EMITS","object":"event:rebook_created","domain":"lead","evidence":"anchored"} {"id":"rel:br_fu_001_applies_follow_up","type":"Relation","subject":"rule:BR-FU-001","predicate":"APPLIES_TO","object":"action:start_follow_up_task","domain":"lead","evidence":"anchored"} {"id":"rel:br_fu_002_applies_create_appointment","type":"Relation","subject":"rule:BR-FU-002","predicate":"APPLIES_TO","object":"action:create_appointment","domain":"lead","evidence":"anchored"} -{"id":"entity:booking_agent_session","type":"Entity","name":"BookingAgentSession","nameZh":"智能预约短期会话","domain":"booking_agent","jpa":false,"fields":["id","session_id","customer_user_id","store_id","status","draft_json","draft_version","entry_source","input_modality","expires_at","create_time","update_time"],"systems":["Backend Core","Customer Experience FE"],"code":"","doc":"docs/ontology/objects.md#entity:booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned","note":"Batch 0 冻结设计;尚未创建 JPA 实体,实现批需将 jpa 与证据切换为真实状态"} -{"id":"action:create_booking_agent_session","type":"Action","name":"createBookingAgentSession","nameZh":"创建智能预约会话","domain":"booking_agent","inputs":["storeId","entity:current_user"],"outputs":["entity:booking_agent_session","event:booking_agent_started"],"entrypoints":["planned POST /api/booking-agent/sessions"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003","rule:BR-BA-005","rule:BR-BE-001"],"code":"","doc":"docs/ontology/actions.md#action:create_booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"action:submit_booking_agent_message","type":"Action","name":"submitBookingAgentMessage","nameZh":"提交智能预约消息","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","draftVersion","inputType","text"],"outputs":["entity:booking_agent_session","event:booking_agent_draft_ready"],"entrypoints":["planned POST /api/booking-agent/sessions/{sessionId}/messages"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-002","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"","doc":"docs/ontology/actions.md#action:submit_booking_agent_message; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"action:transcribe_booking_agent_audio","type":"Action","name":"transcribeBookingAgentAudio","nameZh":"转写智能预约语音","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","audio"],"outputs":["editableText"],"entrypoints":["planned POST /api/booking-agent/sessions/{sessionId}/transcriptions"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-004","rule:BR-BA-005"],"code":"","doc":"docs/ontology/actions.md#action:transcribe_booking_agent_audio; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"action:handoff_booking_agent_draft","type":"Action","name":"handoffBookingAgentDraft","nameZh":"交接智能预约草稿","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","draftVersion"],"outputs":["entity:booking_agent_session","event:booking_agent_fallback"],"entrypoints":["planned POST /api/booking-agent/sessions/{sessionId}/fallback"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"","doc":"docs/ontology/actions.md#action:handoff_booking_agent_draft; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"action:cancel_booking_agent_session","type":"Action","name":"cancelBookingAgentSession","nameZh":"结束智能预约会话","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user"],"outputs":["entity:booking_agent_session"],"entrypoints":["planned DELETE /api/booking-agent/sessions/{sessionId}"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003"],"code":"","doc":"docs/ontology/actions.md#action:cancel_booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"event:booking_agent_started","type":"Event","name":"BookingAgentStarted","nameZh":"智能预约会话已启动","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","entrySource","occurredAt"],"emittedBy":["action:create_booking_agent_session"],"appliesRules":["rule:BR-BA-001","rule:BR-BA-004","rule:BR-BE-001"],"code":"","doc":"docs/ontology/events.md#event:booking_agent_started; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"event:booking_agent_draft_ready","type":"Event","name":"BookingAgentDraftReady","nameZh":"智能预约草稿已就绪","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","inputModality","occurredAt"],"emittedBy":["action:submit_booking_agent_message"],"appliesRules":["rule:BR-BA-002","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BE-001"],"code":"","doc":"docs/ontology/events.md#event:booking_agent_draft_ready; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"event:booking_agent_fallback","type":"Event","name":"BookingAgentFallback","nameZh":"智能预约已降级交接","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","reason","occurredAt"],"emittedBy":["action:handoff_booking_agent_draft"],"appliesRules":["rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"","doc":"docs/ontology/events.md#event:booking_agent_fallback; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rule:BR-BA-001","type":"Rule","name":"bookingAgentCustomerStoreBoundary","nameZh":"智能预约 customer 与门店边界","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft","action:cancel_booking_agent_session"],"code":"","doc":"docs/ontology/rules.md#rule:BR-BA-001; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rule:BR-BA-002","type":"Rule","name":"bookingAgentModelNonAuthority","nameZh":"模型非权威与结构强校验","domain":"booking_agent","appliesTo":["action:submit_booking_agent_message"],"code":"","doc":"docs/ontology/rules.md#rule:BR-BA-002; docs/contracts/booking-intent-v1.schema.json; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rule:BR-BA-003","type":"Rule","name":"bookingAgentStateVersionTtlNoWrite","nameZh":"会话状态版本 TTL 与无写路径","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:handoff_booking_agent_draft","action:cancel_booking_agent_session"],"code":"","doc":"docs/ontology/rules.md#rule:BR-BA-003; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rule:BR-BA-004","type":"Rule","name":"bookingAgentPrivacyMinimization","nameZh":"语音原文与日志最小化","domain":"booking_agent","appliesTo":["action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft"],"code":"","doc":"docs/ontology/rules.md#rule:BR-BA-004; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rule:BR-BA-005","type":"Rule","name":"bookingAgentDisabledRateLimitedFallback","nameZh":"默认关闭限流与可退化","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft"],"code":"","doc":"docs/ontology/rules.md#rule:BR-BA-005; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"","evidence":"documented","status":"planned"} -{"id":"rel:user_has_booking_agent_session","type":"Relation","subject":"entity:user","predicate":"HAS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:store_has_booking_agent_session","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:create_booking_agent_session_creates_session","type":"Relation","subject":"action:create_booking_agent_session","predicate":"CREATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:submit_booking_agent_message_updates_session","type":"Relation","subject":"action:submit_booking_agent_message","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:transcribe_booking_agent_audio_reads_session","type":"Relation","subject":"action:transcribe_booking_agent_audio","predicate":"READS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:handoff_booking_agent_draft_updates_session","type":"Relation","subject":"action:handoff_booking_agent_draft","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:cancel_booking_agent_session_updates_session","type":"Relation","subject":"action:cancel_booking_agent_session","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"documented"} -{"id":"rel:create_booking_agent_session_emits_started","type":"Relation","subject":"action:create_booking_agent_session","predicate":"EMITS","object":"event:booking_agent_started","domain":"booking_agent","evidence":"documented"} -{"id":"rel:submit_booking_agent_message_emits_draft_ready","type":"Relation","subject":"action:submit_booking_agent_message","predicate":"EMITS","object":"event:booking_agent_draft_ready","domain":"booking_agent","evidence":"documented"} -{"id":"rel:handoff_booking_agent_draft_emits_fallback","type":"Relation","subject":"action:handoff_booking_agent_draft","predicate":"EMITS","object":"event:booking_agent_fallback","domain":"booking_agent","evidence":"documented"} +{"id":"entity:booking_agent_session","type":"Entity","name":"BookingAgentSession","nameZh":"智能预约短期会话","domain":"booking_agent","jpa":true,"fields":["id","session_id","customer_user_id","store_id","status","draft_json","draft_version","entry_source","input_modality","expires_at","create_time","update_time"],"systems":["Backend Core","Customer Experience FE"],"code":"backend/src/main/java/com/petstore/bookingagent/domain/BookingAgentSession.java; backend/src/main/java/com/petstore/bookingagent/mapper/BookingAgentSessionMapper.java","doc":"docs/ontology/objects.md#entity:booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentSessionMigrationTest; BookingAgentServiceTest; BookingAgentSessionJanitorTest","evidence":"anchored","status":"implemented","note":"M0 短期结构化草稿;不保存原文/音频/模型响应,不关联 Appointment"} +{"id":"action:create_booking_agent_session","type":"Action","name":"createBookingAgentSession","nameZh":"创建智能预约会话","domain":"booking_agent","inputs":["storeId","entity:current_user"],"outputs":["entity:booking_agent_session","event:booking_agent_started"],"entrypoints":["POST /api/booking-agent/sessions"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003","rule:BR-BA-005","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java","doc":"docs/ontology/actions.md#action:create_booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"action:submit_booking_agent_message","type":"Action","name":"submitBookingAgentMessage","nameZh":"提交智能预约消息","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","draftVersion","inputType","text"],"outputs":["entity:booking_agent_session","event:booking_agent_draft_ready"],"entrypoints":["POST /api/booking-agent/sessions/{sessionId}/messages"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-002","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentContextResolver.java","doc":"docs/ontology/actions.md#action:submit_booking_agent_message; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentServiceTest; BookingAgentContextResolverTest","evidence":"anchored","status":"implemented"} +{"id":"action:transcribe_booking_agent_audio","type":"Action","name":"transcribeBookingAgentAudio","nameZh":"转写智能预约语音","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","audio"],"outputs":["editableText"],"entrypoints":["POST /api/booking-agent/sessions/{sessionId}/transcriptions"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-004","rule:BR-BA-005"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentAudioValidator.java","doc":"docs/ontology/actions.md#action:transcribe_booking_agent_audio; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentAudioValidatorTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"action:handoff_booking_agent_draft","type":"Action","name":"handoffBookingAgentDraft","nameZh":"交接智能预约草稿","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user","draftVersion"],"outputs":["entity:booking_agent_session","event:booking_agent_fallback"],"entrypoints":["POST /api/booking-agent/sessions/{sessionId}/fallback"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java","doc":"docs/ontology/actions.md#action:handoff_booking_agent_draft; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"action:cancel_booking_agent_session","type":"Action","name":"cancelBookingAgentSession","nameZh":"结束智能预约会话","domain":"booking_agent","inputs":["entity:booking_agent_session","entity:current_user"],"outputs":["entity:booking_agent_session"],"entrypoints":["DELETE /api/booking-agent/sessions/{sessionId}"],"appliesRules":["rule:BR-AUTH-001","rule:BR-BA-001","rule:BR-BA-003"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java","doc":"docs/ontology/actions.md#action:cancel_booking_agent_session; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"event:booking_agent_started","type":"Event","name":"BookingAgentStarted","nameZh":"智能预约会话已启动","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","entrySource","occurredAt"],"emittedBy":["action:create_booking_agent_session"],"appliesRules":["rule:BR-BA-001","rule:BR-BA-004","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java","doc":"docs/ontology/events.md#event:booking_agent_started; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BusinessEventServiceTest","evidence":"anchored","status":"implemented"} +{"id":"event:booking_agent_draft_ready","type":"Event","name":"BookingAgentDraftReady","nameZh":"智能预约草稿已就绪","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","inputModality","occurredAt"],"emittedBy":["action:submit_booking_agent_message"],"appliesRules":["rule:BR-BA-002","rule:BR-BA-003","rule:BR-BA-004","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java","doc":"docs/ontology/events.md#event:booking_agent_draft_ready; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BusinessEventServiceTest","evidence":"anchored","status":"implemented"} +{"id":"event:booking_agent_fallback","type":"Event","name":"BookingAgentFallback","nameZh":"智能预约已降级交接","domain":"booking_agent","payload":["storeId","aggregateType","aggregateId","actorUserId","actorRole","source","reason","occurredAt"],"emittedBy":["action:handoff_booking_agent_draft"],"appliesRules":["rule:BR-BA-003","rule:BR-BA-004","rule:BR-BA-005","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java","doc":"docs/ontology/events.md#event:booking_agent_fallback; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BusinessEventServiceTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"rule:BR-BA-001","type":"Rule","name":"bookingAgentCustomerStoreBoundary","nameZh":"智能预约 customer 与门店边界","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft","action:cancel_booking_agent_session"],"code":"backend/src/main/java/com/petstore/bookingagent/api/BookingAgentController.java; backend/src/main/java/com/petstore/bookingagent/mapper/BookingAgentSessionMapper.java","doc":"docs/ontology/rules.md#rule:BR-BA-001; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentControllerTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"rule:BR-BA-002","type":"Rule","name":"bookingAgentModelNonAuthority","nameZh":"模型非权威与结构强校验","domain":"booking_agent","appliesTo":["action:submit_booking_agent_message"],"code":"backend/src/main/java/com/petstore/bookingagent/provider/BookingIntentValidator.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentContextResolver.java","doc":"docs/ontology/rules.md#rule:BR-BA-002; docs/contracts/booking-intent-v1.schema.json; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingIntentValidatorTest; BookingAgentContextResolverTest","evidence":"anchored","status":"implemented"} +{"id":"rule:BR-BA-003","type":"Rule","name":"bookingAgentStateVersionTtlNoWrite","nameZh":"会话状态版本 TTL 与无写路径","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:handoff_booking_agent_draft","action:cancel_booking_agent_session"],"code":"backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentSessionJanitor.java","doc":"docs/ontology/rules.md#rule:BR-BA-003; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentServiceTest; BookingAgentSessionJanitorTest; BookingAgentNoAppointmentWriteContractTest","evidence":"anchored","status":"implemented"} +{"id":"rule:BR-BA-004","type":"Rule","name":"bookingAgentPrivacyMinimization","nameZh":"语音原文与日志最小化","domain":"booking_agent","appliesTo":["action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft"],"code":"backend/src/main/java/com/petstore/bookingagent/service/BookingAgentAudioValidator.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentService.java","doc":"docs/ontology/rules.md#rule:BR-BA-004; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentAudioValidatorTest; BookingAgentSensitiveLoggingContractTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"rule:BR-BA-005","type":"Rule","name":"bookingAgentDisabledRateLimitedFallback","nameZh":"默认关闭限流与可退化","domain":"booking_agent","appliesTo":["action:create_booking_agent_session","action:submit_booking_agent_message","action:transcribe_booking_agent_audio","action:handoff_booking_agent_draft"],"code":"backend/src/main/java/com/petstore/bookingagent/config/BookingAgentProperties.java; backend/src/main/java/com/petstore/bookingagent/service/BookingAgentRateLimiter.java","doc":"docs/ontology/rules.md#rule:BR-BA-005; docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md","test":"BookingAgentProviderConfigurationTest; BookingAgentRateLimiterTest; BookingAgentServiceTest","evidence":"anchored","status":"implemented"} +{"id":"rel:user_has_booking_agent_session","type":"Relation","subject":"entity:user","predicate":"HAS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:store_has_booking_agent_session","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:create_booking_agent_session_creates_session","type":"Relation","subject":"action:create_booking_agent_session","predicate":"CREATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:submit_booking_agent_message_updates_session","type":"Relation","subject":"action:submit_booking_agent_message","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:transcribe_booking_agent_audio_reads_session","type":"Relation","subject":"action:transcribe_booking_agent_audio","predicate":"READS","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:handoff_booking_agent_draft_updates_session","type":"Relation","subject":"action:handoff_booking_agent_draft","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:cancel_booking_agent_session_updates_session","type":"Relation","subject":"action:cancel_booking_agent_session","predicate":"UPDATES","object":"entity:booking_agent_session","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:create_booking_agent_session_emits_started","type":"Relation","subject":"action:create_booking_agent_session","predicate":"EMITS","object":"event:booking_agent_started","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:submit_booking_agent_message_emits_draft_ready","type":"Relation","subject":"action:submit_booking_agent_message","predicate":"EMITS","object":"event:booking_agent_draft_ready","domain":"booking_agent","evidence":"anchored"} +{"id":"rel:handoff_booking_agent_draft_emits_fallback","type":"Relation","subject":"action:handoff_booking_agent_draft","predicate":"EMITS","object":"event:booking_agent_fallback","domain":"booking_agent","evidence":"anchored"} diff --git a/ontology/README.md b/ontology/README.md index be676c4..bef0351 100644 --- a/ontology/README.md +++ b/ontology/README.md @@ -61,7 +61,7 @@ rel:subject_predicate_object # rel:report_belongs_to_appointment 已覆盖 P0 主闭环实体与动作,并补录:`Pet`、`ServiceType`、`ScheduleBlock`、`HighlightVideo`、`SessionToken`、`ReportImage`、`ReportTestimonial`、`ServiceInterval`、`HighlightFailReason`、`CurrentUser`、`FollowUpTask`,以及成片/上传/鉴权/寄语/退订/回访状态机/真实再次预约相关规则。 -**智能预约助手 M0** 已以 `documented` 证据录入 `BookingAgentSession`、5 个动作、3 个漏斗事件、`BR-BA-001..005` 和 10 条关系。它们表示 Batch 0 契约已冻结,**不表示代码已实现**;五个计划 endpoint 以 `planned METHOD /path` 录入,实现时必须与代码、测试同批转为 `anchored`。 +**智能预约助手 M0 Batch 2** 已以 `anchored` 证据录入真实 JPA `BookingAgentSession`、5 个只读编排动作、3 个低敏漏斗事件、`BR-BA-001..005` 和 10 条关系。五个 endpoint 已按冻结 OpenAPI 注册;`/confirm` 与 Appointment 写路径仍明确不存在,供应商开关默认关闭。 **门店 Web 后台 Phase A**:`AdminConsole`、`Workbench`、`WorkbenchTodoItem` 仍是非 JPA 读模型;`StoreCustomer` 与 `FollowUpTask` 已落为 JPA 稳定业务对象。`BR-ADMIN-001`…`004`、`BR-FU-001/002` 与 admin actions 继续约束后台;收银/会员资产仍为 gap。 diff --git a/ontology/actions.md b/ontology/actions.md index 81a2a64..a9d26d7 100644 --- a/ontology/actions.md +++ b/ontology/actions.md @@ -716,51 +716,56 @@ legacy 永久邀请码查询入口已停用,固定返回 `410 / LEGACY_INVITE_ - **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-SCH-001` - **证据**:`anchored` -## 智能预约助手 M0(Batch 0 · documented) +## 智能预约助手 M0(Batch 2 · anchored) -> 下列入口已冻结但尚未注册;机器图以 `planned METHOD /path` 标识,不得误报为已实现 endpoint。 +> 下列五个入口已按冻结 OpenAPI 注册;仍不存在 `/confirm`,不进入 Appointment 写路径。 ### action:create_booking_agent_session 使用已选有效 `storeId` 为当前 customer 创建短期会话,并首次记录 `booking_agent_started`。 -- **计划入口**:`POST /api/booking-agent/sessions` +- **入口**:`POST /api/booking-agent/sessions` - **输出**:`entity:booking_agent_session` - **规则**:`rule:BR-AUTH-001`、`rule:BR-BA-001`、`rule:BR-BA-003`、`rule:BR-BA-005`、`rule:BR-BE-001` -- **证据**:`documented` +- **代码/测试**:`BookingAgentController#createSession`、`BookingAgentService#createSession`;`BookingAgentControllerTest`、`BookingAgentServiceTest` +- **证据**:`anchored` ### action:submit_booking_agent_message 在 `sessionId + current.userId` 范围内校验草稿版本,将当轮文字解析为非权威约束,再由后端解析真实宠物、服务与号源。 -- **计划入口**:`POST /api/booking-agent/sessions/{sessionId}/messages` +- **入口**:`POST /api/booking-agent/sessions/{sessionId}/messages` - **输入/输出**:`entity:booking_agent_session`、`draftVersion`、`inputType`、`text` - **规则**:`rule:BR-BA-001` … `rule:BR-BA-005` - **触发事件**:首次进入 `confirmable` 时 `event:booking_agent_draft_ready` -- **证据**:`documented` +- **代码/测试**:`BookingAgentController#submitMessage`、`BookingAgentService#submitMessage`、`BookingAgentContextResolver`;`BookingAgentServiceTest`、`BookingAgentContextResolverTest` +- **证据**:`anchored` ### action:transcribe_booking_agent_audio 在本人未终止 session 下将短音频转为可编辑文字,不自动进入意图处理。 -- **计划入口**:`POST /api/booking-agent/sessions/{sessionId}/transcriptions` +- **入口**:`POST /api/booking-agent/sessions/{sessionId}/transcriptions` - **规则**:`rule:BR-BA-001`、`rule:BR-BA-004`、`rule:BR-BA-005` -- **证据**:`documented` +- **代码/测试**:`BookingAgentController#transcribe`、`BookingAgentAudioValidator`;`BookingAgentControllerTest`、`BookingAgentAudioValidatorTest` +- **证据**:`anchored` ### action:handoff_booking_agent_draft 返回服务端验证的部分或完整草稿,将会话转为 `fallback`,由现有表单承担最终提交。 -- **计划入口**:`POST /api/booking-agent/sessions/{sessionId}/fallback` +- **入口**:`POST /api/booking-agent/sessions/{sessionId}/fallback` - **输出**:一次性草稿交接对象 - **规则**:`rule:BR-BA-001`、`rule:BR-BA-003`、`rule:BR-BA-005` - **触发事件**:`event:booking_agent_fallback` -- **证据**:`documented` +- **代码/测试**:`BookingAgentController#handoff`、`BookingAgentService#handoff`;`BookingAgentControllerTest`、`BookingAgentServiceTest` +- **证据**:`anchored` ### action:cancel_booking_agent_session 由当前 customer 结束本人未终止会话,仅将 session 标记为 `cancelled`。 -- **计划入口**:`DELETE /api/booking-agent/sessions/{sessionId}` +- **入口**:`DELETE /api/booking-agent/sessions/{sessionId}` - **规则**:`rule:BR-BA-001`、`rule:BR-BA-003` -- **证据**:`documented` +- **代码/测试**:`BookingAgentController#cancel`、`BookingAgentService#cancel`;`BookingAgentControllerTest`、`BookingAgentServiceTest` +- **证据**:`anchored` diff --git a/ontology/events.md b/ontology/events.md index b89d772..d058868 100644 --- a/ontology/events.md +++ b/ontology/events.md @@ -338,7 +338,7 @@ - **payload**:`storeId`、`userId`、`ts` - **证据**:`gap`(A 期可不实现上报) -## 智能预约助手 M0(Batch 0 · documented) +## 智能预约助手 M0(Batch 2 · anchored) ### event:booking_agent_started @@ -348,7 +348,8 @@ - **幂等键**:`booking_agent_started:{sessionDbId}` - **BusinessEvent**:`storeId=session.storeId`、`aggregateType=booking_agent_session`、`aggregateId=session.id` - **metadata 白名单**:`entrySource` -- **证据**:`documented` +- **代码/测试**:`BusinessEventService#recordBookingAgentStarted`;`BusinessEventServiceTest` +- **证据**:`anchored` ### event:booking_agent_draft_ready @@ -357,7 +358,8 @@ session 首次进入 `confirmable` 的事实,不表示预约已创建。 - **触发动作**:`action:submit_booking_agent_message` - **幂等键**:`booking_agent_draft_ready:{sessionDbId}` - **metadata 白名单**:`inputModality` -- **证据**:`documented` +- **代码/测试**:`BusinessEventService#recordBookingAgentDraftReady`;`BusinessEventServiceTest` +- **证据**:`anchored` ### event:booking_agent_fallback @@ -366,6 +368,7 @@ session 首次进入 `confirmable` 的事实,不表示预约已创建。 - **触发动作**:`action:handoff_booking_agent_draft` - **幂等键**:`booking_agent_fallback:{sessionDbId}` - **metadata 白名单**:`reason=user/llm_unavailable` -- **证据**:`documented` +- **代码/测试**:`BusinessEventService#recordBookingAgentFallback`;`BusinessEventServiceTest` +- **证据**:`anchored` M0 三个事件均禁止写入输入原文、转写文本、备注、宠物名、服务名、供应商 request ID 或错误原文;M0 不定义 `booking_agent_confirmed`。 diff --git a/ontology/objects.md b/ontology/objects.md index d14a48b..c2b99ca 100644 --- a/ontology/objects.md +++ b/ontology/objects.md @@ -543,7 +543,7 @@ API 动态聚合字段:`pets`、`lastVisitAt`、`lastReportId`、`leadStatus` --- -## 智能预约助手 M0(Batch 0 契约) +## 智能预约助手 M0(Batch 2 已实现) ## entity:booking_agent_session @@ -563,7 +563,10 @@ API 动态聚合字段:`pets`、`lastVisitAt`、`lastReportId`、`leadStatus` | `create_time / update_time` | 服务端审计时间 | - **不存储**:原始语音、转写原文、完整对话、提示词、模型原始请求/响应、`appointment_id`。 -- **归属系统**:Backend Core(计划 `bookingagent` 包)。 +- **归属系统**:Backend Core(`bookingagent` 包)。 +- **代码**:`backend/src/main/java/com/petstore/bookingagent/domain/BookingAgentSession.java`、`mapper/BookingAgentSessionMapper.java`。 +- **迁移**:`backend/db/migrations/20260802_create_booking_agent_session.sql`。 +- **测试**:`BookingAgentSessionMigrationTest`、`BookingAgentServiceTest`、`BookingAgentSessionJanitorTest`。 - **文档**:`docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md`。 -- **证据**:`documented`(尚未创建 JPA 实体,机器图临时 `jpa:false`;实现批必须切换为真实 JPA 证据)。 +- **证据**:`anchored`(真实 JPA 实体、迁移、owner 查询与 TTL 清理测试)。 - **约束**:`rule:BR-BA-001` … `rule:BR-BA-005`。 diff --git a/ontology/relations.md b/ontology/relations.md index fc43463..620f526 100644 --- a/ontology/relations.md +++ b/ontology/relations.md @@ -188,7 +188,7 @@ | `view_store_customer_timeline` READS `store_customer` / `business_event` | 同店校验后分页读取主档与事实 | | `admin_list_leads` READS `follow_up_task` | `/leads` 导航承载真实操作任务池,不直接编辑留资事实 | -## 智能预约助手 M0 关系(Batch 0 · documented) +## 智能预约助手 M0 关系(Batch 2 · anchored) | 关系 | 说明 | |------|------| diff --git a/ontology/rules.md b/ontology/rules.md index da541b7..ef79522 100644 --- a/ontology/rules.md +++ b/ontology/rules.md @@ -384,7 +384,7 @@ ### rule:BR-CONFIG-003 -**生产发布只读门禁**:正式切流前必须按固定顺序完成备份和八个版本化迁移,再以最终生产配置运行 JPA schema `validate` 与 31 项只读数据不变量检查。运行期 readiness 必须同时覆盖数据库、磁盘、上传目录和 FFmpeg/ffprobe;上线自动 smoke 只允许 GET,不创建或修改业务数据,并验证 CORS allow/deny。 +**生产发布只读门禁**:正式切流前必须完成备份,并按固定顺序执行目标版本包含的全部版本化迁移,再以最终生产配置运行 JPA schema `validate` 与 31 项只读数据不变量检查。运行期 readiness 必须同时覆盖数据库、磁盘、上传目录和 FFmpeg/ffprobe;上线自动 smoke 只允许 GET,不创建或修改业务数据,并验证 CORS allow/deny。 - **适用动作**:—(发布全局约束) - **代码**:`backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java`、`PetstoreRuntimeHealthIndicator.java`、`backend/deploy/release-preflight.sh`、`production-smoke.sh` @@ -480,39 +480,44 @@ - **测试**:`BusinessEventServiceTest`、`BusinessEventMigrationTest`、`AdminBusinessEventControllerTest` - **证据**:`anchored` -## 智能预约助手 M0(Batch 0 · documented) +## 智能预约助手 M0(Batch 2 · anchored) ### rule:BR-BA-001 **customer 与门店数据边界**:所有 endpoint 只允许已登录 customer;session 每次以 `sessionId + current.userId` 查询;他人与不存在 session 同样返回 `SESSION_NOT_FOUND`。`storeId` 只在创建时验证,后续不可覆盖。 - **适用动作**:M0 全部五个动作 -- **证据**:`documented` +- **代码/测试**:`BookingAgentController`、`BookingAgentSessionMapper#findOwnedForUpdate`;`BookingAgentControllerTest`、`BookingAgentServiceTest` +- **证据**:`anchored` ### rule:BR-BA-002 **模型非权威与结构强校验**:模型只返回通过 `booking-intent-v1` 的文本约束,未知字段拒绝。宠物、服务、日期时间和号源由后端确定性重新解析;模型不得产生 ID、价格、号源或预约状态事实。 - **适用动作**:`action:submit_booking_agent_message` -- **证据**:`documented` +- **代码/测试**:`BookingIntentValidator`、`BookingAgentIntentMerger`、`BookingAgentContextResolver`;对应 provider/context 测试 +- **证据**:`anchored` ### rule:BR-BA-003 **状态、版本、TTL 与无写路径**:M0 只允许六个会话状态,草稿更新必须校验 `draftVersion`;TTL 固定 30 分钟且不滑动续期,过期 24 小时后物理清理。M0 不注册 `/confirm`、不调用预约写服务、不修改 `Appointment`。 - **适用动作**:创建、提交消息、草稿交接、结束会话 -- **证据**:`documented` +- **代码/测试**:`BookingAgentService`、`BookingAgentSessionJanitor`;`BookingAgentServiceTest`、`BookingAgentSessionJanitorTest`、`BookingAgentNoAppointmentWriteContractTest` +- **证据**:`anchored` ### rule:BR-BA-004 **语音、原文与日志最小化**:原始语音、当轮用户原文、完整对话、提示词和模型原始请求/响应不落库、不进事件、不进日志。音频最多 60 秒/3 MB,转写完立即释放,且必须经用户编辑/确认后才发消息。 - **适用动作**:提交消息、语音转写、草稿交接 -- **证据**:`documented` +- **代码/测试**:`BookingAgentAudioValidator`、`BookingAgentService#transcribe`;`BookingAgentAudioValidatorTest`、`BookingAgentSensitiveLoggingContractTest` +- **证据**:`anchored` ### rule:BR-BA-005 **默认关闭、限流与可退化**:`PETSTORE_BOOKING_AGENT_ENABLED` 默认 `false`。每 customer 10 分钟内最多创建 5 会话、30 条消息、10 段语音;模型/ASR/外部查询失败时保留已验证草稿并退回普通表单,不影响普通预约。单实例进程内限流只是 M0 临时边界,多实例前必须改为共享状态。 - **适用动作**:创建会话、提交消息、语音转写、草稿交接 -- **证据**:`documented` +- **代码/测试**:`BookingAgentProperties`、`BookingAgentRateLimiter`、provider 错误映射;`BookingAgentProviderConfigurationTest`、`BookingAgentRateLimiterTest`、`BookingAgentServiceTest` +- **证据**:`anchored` diff --git a/智能预约助手-M0执行队列-2026-08-02.md b/智能预约助手-M0执行队列-2026-08-02.md index 899f89a..b223712 100644 --- a/智能预约助手-M0执行队列-2026-08-02.md +++ b/智能预约助手-M0执行队列-2026-08-02.md @@ -1,7 +1,7 @@ # 智能预约助手 M0 执行队列 > 日期:2026-08-02
-> 状态:**Batch 1 provider adaptation completed / Batch 2 ready**
+> 状态:**Batch 2 session orchestration completed / Batch 3 ready**
> RC Included:`No`
> 当前优先级说明:不抢占 `phase2-pilot-rc6` 真实门店基线问题;按独立 feature flag 开发 @@ -17,7 +17,7 @@ | Task Name | Owner Agent | Status | Depends On | |---|---|---|---| | 实现智能预约供应商适配与契约测试 | Backend Core | Done | Batch 0 冻结资产 | -| 实现智能预约会话与只读预约编排 | Backend Core | Ready | Batch 0 冻结资产;provider interface | +| 实现智能预约会话与只读预约编排 | Backend Core | Done | Batch 0 冻结资产;provider interface | | 实现宠主一句话预约与草稿回填 | Customer Experience FE | Ready | OpenAPI;可先用 mock | | 验收智能预约 M0 草稿链路 | Core Flow QA | Ready | 可先写 fixture/smoke;执行依赖前三项 | | 完成真实供应商对照与生产隐私复核 | Backend Ops | Blocked | 云账号、密钥、授权语音和上线授权 | @@ -71,7 +71,7 @@ | Paired QA | `Core Flow QA` | | Workstream | `Core Booking Flow` | | Track | `Coding` | -| Status | `Ready` | +| Status | `Done — 2026-08-02` | | Priority | `P0` | | Repo/Path | `backend/src/main/java/com/petstore/bookingagent`、`backend/db/migrations/20260802_create_booking_agent_session.sql` | | Service Lock | `backend:booking-agent-session+appointment-read-tools` | @@ -79,11 +79,11 @@ | Acceptance | 五个 endpoint;customer 归属;六态状态机;draftVersion;30 分钟 TTL/24 小时清理;只读宠物/服务/容量;三个低敏事件;不存在 `/confirm` | | RC Included | `No` | | Due Date | `TBD` | -| Output Link | `TBD — implementation commit` | +| Output Link | [`petstore-backend@3c00065`](https://git.s-good.com/petstore/petstore-backend/commit/3c00065) | | Blocker Owner | `None` | -| Commit / RC | `Not Frozen` | +| Commit / RC | `3c00065 / RC No` | | Architecture Review Required | `Yes — completed by M0 ADR` | -| Data Model Review Required | `Yes — proposal completed; implementation diff requires Data Model review` | +| Data Model Review Required | `Yes — implementation diff completed; frozen schema unchanged` | | Access / Privacy Review Required | `Yes — development boundary completed` | 额外门禁: @@ -94,6 +94,15 @@ - 迁移包含索引、状态验证和 TTL 清理验证查询; - 实现时同步把对应本体证据从 `documented` 更新为 `anchored`。 +完成证据: + +- 五个冻结 endpoint 已注册,全部要求 customer;session 使用 `sessionId + current.userId` 带锁查询; +- 六态、`draftVersion`、固定 30 分钟 TTL、每小时过期和 24 小时物理清理已实现; +- Pet、ServiceType、日期时间和号源均由后端在 owner/store 范围重新解析,容量查询复用现有只读模型; +- 音频按 3 MB、文件头、MIME 和 60 秒权威复核,转写不自动提交消息; +- 三个低敏 BusinessEvent 已实现,`draft_json` 不含原文、查询词、音频或模型响应; +- `mvn test`:292 tests,0 failures,0 errors;无 `/confirm`,契约测试禁止 `AppointmentMapper.save`。 + ## Task:实现宠主一句话预约与草稿回填 | 字段 | 值 | diff --git a/智能预约助手-M0编码任务brief-2026-08-02.md b/智能预约助手-M0编码任务brief-2026-08-02.md index 194f83b..2b15a14 100644 --- a/智能预约助手-M0编码任务brief-2026-08-02.md +++ b/智能预约助手-M0编码任务brief-2026-08-02.md @@ -1,7 +1,7 @@ # 智能预约助手 M0 编码任务 brief > 日期:2026-08-02
-> 状态:**Batch 1 已完成 / Batch 2 待实施**
+> 状态:**Batch 2 已完成 / Batch 3 待实施**
> 目标:宠主用文字或语音生成可验证预约草稿,回填现有表单,不由智能层创建 `Appointment`
> Workstream:`Core Booking Flow`
> Owner Agent:`Backend Core` + `Customer Experience FE`
@@ -324,16 +324,16 @@ M0 已冻结事件: M0 不记录 `booking_agent_confirmed`,也不修改现有 `appointment_created` metadata,因为助手没有直接确认写入,而客户端的 `source` 字符串不能当作服务端归因事实。如后续需要精确联结「助手草稿 -> 表单创建」,另立 brief 设计可验证、一次性的草稿交接机制。 -Batch 0 已按 `documented` 证据增加: +Batch 0 已按 `documented` 证据增加以下设计条目;Batch 2 已用真实代码和测试将对应条目全部更新为 `anchored`: -- `ontology/objects.md`:`booking_agent_session` 的 `documented` 模型条目; +- `ontology/objects.md`:`booking_agent_session` 最初的 `documented` 模型条目; - `ontology/actions.md`:创建会话、提交消息、语音转写、回填草稿、结束会话; - `ontology/events.md`:M0 三个漏斗事件; - `ontology/rules.md`:customer 数据范围、无写工具、原始语音/原文不持久化、超时降级; - `ontology/relations.md` 与 `graph/ontology.jsonl`; - `coverage/ontology-coverage-audit.md`。 -实现时必须在同一批把真实代码/测试路径补入,并将对应条目从 `documented` 更新为 `anchored`;不得提前宣称已实现。 +Batch 2 已在同一批补入真实代码/测试路径,并将对应条目从 `documented` 更新为 `anchored`;Batch 3 前端仍不得提前宣称已实现。 ## 7. 自动化测试 @@ -411,6 +411,8 @@ Owner:Backend Core Owner:Backend Core +状态:**Completed — 2026-08-02(petstore-backend `3c00065`)** + - 实现 session 实体、迁移、mapper 和 service; - 复用 Pet、ServiceType、Store 和 BookingCapacity 领域服务; - 实现确定性回复模板和 fallback DTO; diff --git a/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md b/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md index 20c73d8..d7c2c37 100644 --- a/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md +++ b/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md @@ -1,7 +1,7 @@ # 架构决策:智能预约助手 M0 契约与数据模型 > 日期:2026-08-02
-> 状态:**Accepted for M0 implementation / 尚未实现**
+> 状态:**Accepted / Batch 2 implemented — petstore-backend `3c00065`**
> Workstream:`Core Booking Flow`
> Track:`Architecture`
> Priority:`P0`(独立试验流,不高于 RC6 真实门店基线)
@@ -9,7 +9,7 @@ > Paired QA:`Core Flow QA`
> Service Lock:`docs:booking-agent-m0-contract+ontology`
> Architecture Review Required:`Yes — completed by this ADR`
-> Data Model Review Required:`Yes — completed for implementation proposal`
+> Data Model Review Required:`Yes — proposal and implementation diff completed`
> Access / Privacy Review Required:`Yes — development boundary accepted; live provider and production review pending`
> RC Included:`No` @@ -159,7 +159,7 @@ stateDiagram-v2 ## 6. 数据模型冻结 -计划迁移:`backend/db/migrations/20260802_create_booking_agent_session.sql`。Batch 0 只冻结设计,不在 docs 任务内创建迁移。 +已实现迁移:`backend/db/migrations/20260802_create_booking_agent_session.sql`(petstore-backend `3c00065`)。 ### 6.1 `t_booking_agent_session` @@ -263,6 +263,8 @@ M0 默认每个 customer: - 去掉 `deleted` 和 `appointment_id`; - 增加固定 TTL、物理清理和 `draft_version` 并发规则。 +**实现 diff 复核通过。** `BookingAgentSession` JPA、迁移与 mapper 保持上述冻结字段和三个索引;六态与 `input_modality` 有数据库 `CHECK`,owner 查询持悲观锁,未增加 `appointment_id`、`deleted`、turn 表或原文列。迁移测试、版本/TTL/清理测试均已落地。 + ### Access / Privacy Review **开发边界通过,真实供应商 smoke 与生产开启未通过。** 不需要真实密钥即可实现接口、stub、状态机和测试;真实调用前仍需完成供应商数据处理确认、小程序隐私告知、费用告警和已知情语音样本评测。 @@ -293,3 +295,11 @@ M0 默认每个 customer: - 未配置密钥或 feature flag 关闭时应用正常启动、普通预约正常; - 本体从 `documented` 更新为 `anchored` 只能与代码和测试同一批完成; - 真实供应商 smoke、生产配置和 RC 纳入必须重新授权。 + +## 12. Batch 2 实现证据 + +- Backend:`3c00065 feat: add booking agent session orchestration`; +- 五个 endpoint、JPA 会话、迁移、owner/store 数据边界、版本与 TTL 已实现; +- 本体 `BookingAgentSession`、五个动作、三个事件、五条规则和十条关系已从 `documented` 更新为 `anchored`; +- 后端全量测试 292 项通过,本体端点 73/73、JPA 实体 17/17,无漂移; +- 功能开关仍默认关闭,真实供应商 smoke、Batch 3 前端和生产开启仍未完成。 diff --git a/生产发布与回滚Runbook-2026-08-01.md b/生产发布与回滚Runbook-2026-08-01.md index d9ddbc7..6c42ae9 100644 --- a/生产发布与回滚Runbook-2026-08-01.md +++ b/生产发布与回滚Runbook-2026-08-01.md @@ -31,7 +31,7 @@ | Priority | P0 | | Repo/Path | `backend/deploy/**`、前端发布配置、`docs/**` | | Service Lock | Petstore backend unit + Petstore 专用 Nginx server blocks + Petstore static roots | -| Depends On | 冻结 RC、真实域名/TLS、备份、八个迁移、微信合法域名 | +| Depends On | 冻结 RC、真实域名/TLS、备份、目标 RC 包含的全部迁移、微信合法域名 | | Acceptance | 预检、构建、readiness、只读 smoke、三角色人工 smoke、回滚演练均有证据 | | RC Included | Yes | | Due Date | TBD | @@ -222,7 +222,7 @@ SMOKE_ALLOWED_ORIGIN=https:// \ ### 9.2 数据库回滚边界 -本批八个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration,也不把已产生的新业务数据覆盖回旧备份。 +RC1~RC6 冻结的八个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。后续 RC 必须把新增迁移一并冻结到 manifest;例如部署包含智能预约会话实体的 backend 前,还必须执行 `20260802_create_booking_agent_session.sql`。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration,也不把已产生的新业务数据覆盖回旧备份。 只有确认发生灾难性数据破坏、已冻结所有写入、明确接受备份时间点之后的数据丢失,并取得用户/业务负责人、DBA 和隐私责任人的书面授权后,才可执行整库恢复。恢复必须先在隔离库验证,再制定专门变更单;本 Runbook 不授权该操作。 @@ -232,7 +232,7 @@ SMOKE_ALLOWED_ORIGIN=https:// \ - 四仓冻结 commit 与产物校验和; - 备份及隔离恢复验证; -- 八个迁移执行和末尾验证结果; +- 目标 RC 包含的全部迁移执行和末尾验证结果; - 最终配置门禁与后端只读预检; - `nginx -t`、readiness、自动只读 smoke; - boss/staff/customer 三角色人工 smoke;