diff --git a/contracts/booking-agent-m0.openapi.yaml b/contracts/booking-agent-m0.openapi.yaml
new file mode 100644
index 0000000..af3d86d
--- /dev/null
+++ b/contracts/booking-agent-m0.openapi.yaml
@@ -0,0 +1,559 @@
+openapi: 3.0.3
+info:
+ title: Petstore Booking Agent M0 API
+ version: 0.1.0
+ description: |
+ 智能预约 M0 冻结契约。M0 只生成并回填预约草稿,不注册 confirm endpoint,
+ 不直接创建 Appointment。除鉴权拦截器的 HTTP 401 外,当前 Petstore 客户端以
+ 响应体 code 作为业务结果;实现不得借本功能顺带重构全局响应语义。
+security:
+ - bearerAuth: []
+x-contract-status: frozen-for-m0
+x-business-error-codes:
+ - UNAUTHENTICATED
+ - FORBIDDEN
+ - AGENT_DISABLED
+ - STORE_NOT_FOUND
+ - SESSION_NOT_FOUND
+ - SESSION_EXPIRED
+ - SESSION_TERMINAL
+ - DRAFT_VERSION_CONFLICT
+ - INVALID_INPUT
+ - INVALID_AUDIO
+ - AUDIO_TOO_LARGE
+ - RATE_LIMITED
+ - AGENT_UNAVAILABLE
+ - ASR_UNAVAILABLE
+paths:
+ /api/booking-agent/sessions:
+ post:
+ operationId: createBookingAgentSession
+ summary: 创建 customer 智能预约会话
+ description: |
+ 仅已登录 customer。M0 要求先在普通预约页选定门店;entrySource 由服务端固定为
+ appointment_create,不从客户端接收。
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateSessionRequest'
+ responses:
+ '200':
+ description: 成功或带业务 code 的可理解失败
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/SessionEnvelope'
+ - $ref: '#/components/schemas/ErrorEnvelope'
+ '401':
+ $ref: '#/components/responses/Unauthenticated'
+
+ /api/booking-agent/sessions/{sessionId}/messages:
+ post:
+ operationId: submitBookingAgentMessage
+ summary: 提交一轮已确认文字并刷新草稿
+ parameters:
+ - $ref: '#/components/parameters/SessionId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SubmitMessageRequest'
+ responses:
+ '200':
+ description: 成功或带业务 code 的可理解失败
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/SessionEnvelope'
+ - $ref: '#/components/schemas/ErrorEnvelope'
+ '401':
+ $ref: '#/components/responses/Unauthenticated'
+
+ /api/booking-agent/sessions/{sessionId}/transcriptions:
+ post:
+ operationId: transcribeBookingAgentAudio
+ summary: 转写当前 customer 会话的一段短音频
+ description: |
+ 只做语音转写,不自动提交消息。客户端必须把返回文字放回可编辑输入框,用户确认后
+ 再调用 messages。后端从 session 解析门店和 customer 范围,不接受客户端上传词表。
+ parameters:
+ - $ref: '#/components/parameters/SessionId'
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ additionalProperties: false
+ required:
+ - audio
+ properties:
+ audio:
+ type: string
+ format: binary
+ description: 单段不超过 60 秒、3 MB;MIME 与真实文件头必须一致
+ responses:
+ '200':
+ description: 成功或带业务 code 的可理解失败
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/TranscriptionEnvelope'
+ - $ref: '#/components/schemas/ErrorEnvelope'
+ '401':
+ $ref: '#/components/responses/Unauthenticated'
+
+ /api/booking-agent/sessions/{sessionId}/fallback:
+ post:
+ operationId: handoffBookingAgentDraft
+ summary: 结束助手并返回可回填普通表单的已验证草稿
+ parameters:
+ - $ref: '#/components/parameters/SessionId'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DraftVersionRequest'
+ responses:
+ '200':
+ description: 成功或带业务 code 的可理解失败
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/HandoffEnvelope'
+ - $ref: '#/components/schemas/ErrorEnvelope'
+ '401':
+ $ref: '#/components/responses/Unauthenticated'
+
+ /api/booking-agent/sessions/{sessionId}:
+ delete:
+ operationId: cancelBookingAgentSession
+ summary: 结束本人智能预约会话
+ parameters:
+ - $ref: '#/components/parameters/SessionId'
+ responses:
+ '200':
+ description: 成功或带业务 code 的可理解失败
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/CancelEnvelope'
+ - $ref: '#/components/schemas/ErrorEnvelope'
+ '401':
+ $ref: '#/components/responses/Unauthenticated'
+
+components:
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: PetstoreSessionToken
+
+ parameters:
+ SessionId:
+ name: sessionId
+ in: path
+ required: true
+ description: 不可预测的 UUID;仍必须和 current.userId 联合查询
+ schema:
+ type: string
+ format: uuid
+
+ responses:
+ Unauthenticated:
+ description: session token 缺失、失效或账号权限已变化
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorEnvelope'
+ example:
+ code: 401
+ message: 登录已失效,请重新登录
+ bizCode: UNAUTHENTICATED
+
+ schemas:
+ CreateSessionRequest:
+ type: object
+ additionalProperties: false
+ required:
+ - storeId
+ properties:
+ storeId:
+ type: integer
+ format: int64
+ minimum: 1
+
+ SubmitMessageRequest:
+ type: object
+ additionalProperties: false
+ required:
+ - inputType
+ - text
+ - draftVersion
+ properties:
+ inputType:
+ type: string
+ enum:
+ - text
+ - voice
+ text:
+ type: string
+ minLength: 1
+ maxLength: 500
+ description: 用户确认后的当轮输入;服务端不得写日志或长期持久化
+ draftVersion:
+ type: integer
+ minimum: 0
+
+ DraftVersionRequest:
+ type: object
+ additionalProperties: false
+ required:
+ - draftVersion
+ properties:
+ draftVersion:
+ type: integer
+ minimum: 0
+
+ BookingAgentStatus:
+ type: string
+ enum:
+ - collecting
+ - proposing
+ - confirmable
+ - fallback
+ - expired
+ - cancelled
+
+ MissingField:
+ type: string
+ enum:
+ - pet
+ - service
+ - date
+ - time
+
+ BookingDraft:
+ type: object
+ additionalProperties: false
+ required:
+ - storeId
+ - storeName
+ - petId
+ - petName
+ - petType
+ - serviceTypeId
+ - serviceType
+ - durationMinutes
+ - dateConstraint
+ - timeWindow
+ - appointmentTime
+ - appointmentEndTime
+ - remark
+ - missingFields
+ properties:
+ storeId:
+ type: integer
+ format: int64
+ storeName:
+ type: string
+ maxLength: 128
+ petId:
+ type: integer
+ format: int64
+ nullable: true
+ petName:
+ type: string
+ nullable: true
+ maxLength: 64
+ petType:
+ type: string
+ nullable: true
+ maxLength: 32
+ serviceTypeId:
+ type: integer
+ format: int64
+ nullable: true
+ serviceType:
+ type: string
+ nullable: true
+ maxLength: 64
+ durationMinutes:
+ type: integer
+ nullable: true
+ minimum: 1
+ dateConstraint:
+ type: string
+ format: date
+ nullable: true
+ timeWindow:
+ $ref: '#/components/schemas/TimeWindow'
+ appointmentTime:
+ $ref: '#/components/schemas/NullableLocalDateTime'
+ appointmentEndTime:
+ $ref: '#/components/schemas/NullableLocalDateTime'
+ remark:
+ type: string
+ nullable: true
+ maxLength: 200
+ missingFields:
+ type: array
+ uniqueItems: true
+ items:
+ $ref: '#/components/schemas/MissingField'
+
+ TimeWindow:
+ type: object
+ nullable: true
+ additionalProperties: false
+ required:
+ - start
+ - end
+ properties:
+ start:
+ type: string
+ nullable: true
+ pattern: '^(?:[01]\d|2[0-3]):[0-5]\d$'
+ end:
+ type: string
+ nullable: true
+ pattern: '^(?:[01]\d|2[0-3]):[0-5]\d$'
+
+ SlotOption:
+ type: object
+ additionalProperties: false
+ required:
+ - startTime
+ - endTime
+ - label
+ properties:
+ startTime:
+ $ref: '#/components/schemas/LocalDateTime'
+ endTime:
+ $ref: '#/components/schemas/LocalDateTime'
+ label:
+ type: string
+ maxLength: 64
+
+ QuickReply:
+ type: object
+ additionalProperties: false
+ required:
+ - type
+ - value
+ - label
+ properties:
+ type:
+ type: string
+ enum:
+ - pet
+ - service
+ - slot
+ - date
+ - fallback
+ value:
+ type: string
+ maxLength: 128
+ label:
+ type: string
+ maxLength: 64
+
+ SessionView:
+ type: object
+ additionalProperties: false
+ required:
+ - sessionId
+ - status
+ - assistantMessage
+ - draft
+ - slotOptions
+ - quickReplies
+ - confirmable
+ - draftVersion
+ - expiresAt
+ properties:
+ sessionId:
+ type: string
+ format: uuid
+ status:
+ $ref: '#/components/schemas/BookingAgentStatus'
+ assistantMessage:
+ type: string
+ maxLength: 300
+ description: 服务端模板渲染,不包含模型思考或供应商错误
+ draft:
+ $ref: '#/components/schemas/BookingDraft'
+ slotOptions:
+ type: array
+ maxItems: 3
+ items:
+ $ref: '#/components/schemas/SlotOption'
+ quickReplies:
+ type: array
+ maxItems: 10
+ items:
+ $ref: '#/components/schemas/QuickReply'
+ confirmable:
+ type: boolean
+ draftVersion:
+ type: integer
+ minimum: 0
+ expiresAt:
+ $ref: '#/components/schemas/LocalDateTime'
+
+ LocalDateTime:
+ type: string
+ pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$'
+ description: Asia/Shanghai 下的本地日期时间,格式 yyyy-MM-ddTHH:mm:ss
+
+ NullableLocalDateTime:
+ type: string
+ nullable: true
+ pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$'
+ description: null 或 Asia/Shanghai 下的本地日期时间;非空时格式为 yyyy-MM-ddTHH:mm:ss
+
+ HandoffData:
+ type: object
+ additionalProperties: false
+ required:
+ - source
+ - draft
+ properties:
+ source:
+ type: string
+ enum:
+ - booking-agent-m0
+ draft:
+ $ref: '#/components/schemas/BookingDraft'
+
+ TranscriptionData:
+ type: object
+ additionalProperties: false
+ required:
+ - text
+ properties:
+ text:
+ type: string
+ maxLength: 500
+ description: 可编辑转写;不返回供应商原始响应或外部 request ID
+
+ CancelData:
+ type: object
+ additionalProperties: false
+ required:
+ - sessionId
+ - status
+ properties:
+ sessionId:
+ type: string
+ format: uuid
+ status:
+ type: string
+ enum:
+ - cancelled
+
+ SessionEnvelope:
+ type: object
+ additionalProperties: false
+ required:
+ - code
+ - data
+ properties:
+ code:
+ type: integer
+ enum:
+ - 200
+ data:
+ $ref: '#/components/schemas/SessionView'
+
+ TranscriptionEnvelope:
+ type: object
+ additionalProperties: false
+ required:
+ - code
+ - data
+ properties:
+ code:
+ type: integer
+ enum:
+ - 200
+ data:
+ $ref: '#/components/schemas/TranscriptionData'
+
+ HandoffEnvelope:
+ type: object
+ additionalProperties: false
+ required:
+ - code
+ - data
+ properties:
+ code:
+ type: integer
+ enum:
+ - 200
+ data:
+ $ref: '#/components/schemas/HandoffData'
+
+ CancelEnvelope:
+ type: object
+ additionalProperties: false
+ required:
+ - code
+ - data
+ properties:
+ code:
+ type: integer
+ enum:
+ - 200
+ data:
+ $ref: '#/components/schemas/CancelData'
+
+ ErrorEnvelope:
+ type: object
+ additionalProperties: false
+ required:
+ - code
+ - message
+ - bizCode
+ properties:
+ code:
+ type: integer
+ enum:
+ - 400
+ - 401
+ - 403
+ - 404
+ - 409
+ - 410
+ - 413
+ - 429
+ - 503
+ message:
+ type: string
+ maxLength: 200
+ bizCode:
+ type: string
+ enum:
+ - UNAUTHENTICATED
+ - FORBIDDEN
+ - AGENT_DISABLED
+ - STORE_NOT_FOUND
+ - SESSION_NOT_FOUND
+ - SESSION_EXPIRED
+ - SESSION_TERMINAL
+ - DRAFT_VERSION_CONFLICT
+ - INVALID_INPUT
+ - INVALID_AUDIO
+ - AUDIO_TOO_LARGE
+ - RATE_LIMITED
+ - AGENT_UNAVAILABLE
+ - ASR_UNAVAILABLE
diff --git a/contracts/booking-intent-v1.schema.json b/contracts/booking-intent-v1.schema.json
new file mode 100644
index 0000000..6aaa935
--- /dev/null
+++ b/contracts/booking-intent-v1.schema.json
@@ -0,0 +1,138 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://petstore.internal/schemas/booking-intent-v1.schema.json",
+ "title": "BookingIntentV1",
+ "description": "智能预约 M0 模型输出。所有字段仅是非权威意图提示,业务 ID、服务事实和号源事实由 Petstore 后端重新解析。",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "schemaVersion",
+ "intent",
+ "petQuery",
+ "serviceQuery",
+ "dateExpression",
+ "timeWindow",
+ "remark",
+ "clearFields",
+ "ambiguities",
+ "nextAction"
+ ],
+ "properties": {
+ "schemaVersion": {
+ "const": "booking-intent-v1"
+ },
+ "intent": {
+ "type": "string",
+ "enum": [
+ "book",
+ "modify",
+ "end",
+ "fallback"
+ ]
+ },
+ "petQuery": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "serviceQuery": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "dateExpression": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "timeWindow": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "start",
+ "end"
+ ],
+ "properties": {
+ "start": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$"
+ },
+ "end": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$"
+ }
+ }
+ }
+ ]
+ },
+ "remark": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "minLength": 1,
+ "maxLength": 200
+ },
+ "clearFields": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 5,
+ "items": {
+ "type": "string",
+ "enum": [
+ "petQuery",
+ "serviceQuery",
+ "dateExpression",
+ "timeWindow",
+ "remark"
+ ]
+ }
+ },
+ "ambiguities": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 5,
+ "items": {
+ "type": "string",
+ "enum": [
+ "pet",
+ "service",
+ "date",
+ "time",
+ "remark"
+ ]
+ }
+ },
+ "nextAction": {
+ "type": "string",
+ "enum": [
+ "ask",
+ "resolve_context",
+ "search_slots",
+ "show_draft",
+ "fallback",
+ "end"
+ ]
+ }
+ }
+}
diff --git a/coverage/ontology-coverage-audit.md b/coverage/ontology-coverage-audit.md
index 70f3381..0736c9b 100644
--- a/coverage/ontology-coverage-audit.md
+++ b/coverage/ontology-coverage-audit.md
@@ -3,7 +3,7 @@
> 本体条目 ↔ 文档章节 ↔ 代码路径 ↔ 测试用例 三方对齐审计。
> 证据强度:`anchored`(三处对齐)/ `documented`(仅文档)/ `implemented`(仅代码)/ `gap`(缺失)。
>
-> **刷新时间**:2026-08-02(FollowUpTask 回访闭环收口后刷新)
+> **刷新时间**:2026-08-02(智能预约 M0 Batch 0 契约冻结后刷新)
## 审计方法
@@ -18,16 +18,16 @@
| 类型 | 数量 |
|------|------|
-| Entity | 29 |
-| Action | 75 |
-| Event | 33 |
-| Rule | 49 |
-| Relation | 102 |
-| **合计** | **288** |
+| Entity | 30 |
+| Action | 80 |
+| Event | 36 |
+| Rule | 55 |
+| Relation | 112 |
+| **合计** | **313** |
-证据强度:`anchored` 249(86.5%)/ `documented` 28 / `gap` 9 / `implemented` 2。
+证据强度:`anchored` 250(79.9%)/ `documented` 52 / `gap` 9 / `implemented` 2。
-> anchored 占比相对 Batch 2 下降,是因为主动录入了 P1/P2 `gap` 占位与客户端埋点 `documented` 条目;端点漂移仍为 0。
+> anchored 占比相对早期快照下降,是因为主动录入了 P1/P2 `gap`、客户端埋点和智能预约 Batch 0 `documented` 条目;端点漂移仍为 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 领域实体 13 个;代码与本体均无单边漂移。
+当前结果:校验通过;HTTP 端点 68/68、JPA Entity 16/16 对齐;非 JPA/计划领域实体 14 个;代码与本体均无单边漂移。智能预约五个未实现端点以 `planned METHOD /path` 建模,不冒充当前代码端点。
## 元数据卫生
@@ -72,9 +72,10 @@ 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 |
| membership 等 5 个 | 方向库 | — | — | gap | roadmap 占位 |
-## 规则覆盖度(49 条)
+## 规则覆盖度(55 条)
| 规则 | 测试 | 证据 |
|------|------|------|
@@ -101,6 +102,7 @@ python3 docs/graph/audit_drift.py
| BR-AUDIT-001 | AuditLogServiceTest / StoreOnboardingMigrationTest | anchored |
| BR-SC-002 客户时间线低敏投影 | StoreCustomerTimelineServiceTest / AdminServiceCustomerControllerTest | anchored |
| BR-FU-001/002 回访状态机与真实再次预约 | FollowUpTaskServiceTest / AppointmentServiceTest / MigrationTest | anchored |
+| BR-BA-001..005 智能预约 M0 边界 | 实现前;ADR + OpenAPI + JSON Schema 已冻结 | documented |
## 待补 gap 清单
@@ -118,6 +120,7 @@ python3 docs/graph/audit_drift.py
| 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg) | 🟡 单测已覆盖状态机与归类 |
| 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap |
| 低 | 废弃 `register_store` / wechat OAuth demo 代码路径 | 🟡 deprecated 已标 |
+| 高 | 智能预约 M0 会话、五个 endpoint、三个事件和五条规则实现证据 | 🟡 documented / Ready;代码+测试同批才转 anchored |
| — | 会员/储值/套餐/历史报告/看板 | gap 占位,不展开 |
## P1/P2 方向(jsonl 已占位)
@@ -144,4 +147,16 @@ python3 docs/graph/audit_drift.py
**页面清单 PRD(已写)**:`docs/门店管理后台-PhaseA-页面清单PRD.md`(A0–A7 全页字段/筛选/API/验收)。
-当前 validate 规模以脚本输出为准(288 条目)。
+当前 validate 规模以脚本输出为准(313 条目)。
+
+## 智能预约 M0 Batch 0 状态(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 |
+
+契约评审已通过,尚未实现。下一实现批必须以真实 code/test 路径替换空证据,并将 `BookingAgentSession` 从计划对象切换为真实 JPA 实体;禁止仅修改 `evidence` 文字。
diff --git a/graph/ontology.jsonl b/graph/ontology.jsonl
index 6e5ee0f..bf4bb00 100644
--- a/graph/ontology.jsonl
+++ b/graph/ontology.jsonl
@@ -287,3 +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"}
diff --git a/ontology/README.md b/ontology/README.md
index abcfaad..be676c4 100644
--- a/ontology/README.md
+++ b/ontology/README.md
@@ -61,6 +61,8 @@ 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`。
+
**门店 Web 后台 Phase A**:`AdminConsole`、`Workbench`、`WorkbenchTodoItem` 仍是非 JPA 读模型;`StoreCustomer` 与 `FollowUpTask` 已落为 JPA 稳定业务对象。`BR-ADMIN-001`…`004`、`BR-FU-001/002` 与 admin actions 继续约束后台;收银/会员资产仍为 gap。
P1/P2 方向(会员/储值/套餐/历史报告/看板)以 `gap` 证据条目录入,不展开字段;受 `rule:BR-ADMIN-004` 约束不得偷渡进 Phase A 设计。
diff --git a/ontology/actions.md b/ontology/actions.md
index 23e0c89..81a2a64 100644
--- a/ontology/actions.md
+++ b/ontology/actions.md
@@ -715,3 +715,52 @@ legacy 永久邀请码查询入口已停用,固定返回 `410 / LEGACY_INVITE_
- **入口**:Admin UI `/schedule`;`GET /api/schedule/day`;`POST/DELETE /api/schedule/block`
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-SCH-001`
- **证据**:`anchored`
+
+## 智能预约助手 M0(Batch 0 · documented)
+
+> 下列入口已冻结但尚未注册;机器图以 `planned METHOD /path` 标识,不得误报为已实现 endpoint。
+
+### action:create_booking_agent_session
+
+使用已选有效 `storeId` 为当前 customer 创建短期会话,并首次记录 `booking_agent_started`。
+
+- **计划入口**:`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`
+
+### action:submit_booking_agent_message
+
+在 `sessionId + current.userId` 范围内校验草稿版本,将当轮文字解析为非权威约束,再由后端解析真实宠物、服务与号源。
+
+- **计划入口**:`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`
+
+### action:transcribe_booking_agent_audio
+
+在本人未终止 session 下将短音频转为可编辑文字,不自动进入意图处理。
+
+- **计划入口**:`POST /api/booking-agent/sessions/{sessionId}/transcriptions`
+- **规则**:`rule:BR-BA-001`、`rule:BR-BA-004`、`rule:BR-BA-005`
+- **证据**:`documented`
+
+### action:handoff_booking_agent_draft
+
+返回服务端验证的部分或完整草稿,将会话转为 `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`
+
+### action:cancel_booking_agent_session
+
+由当前 customer 结束本人未终止会话,仅将 session 标记为 `cancelled`。
+
+- **计划入口**:`DELETE /api/booking-agent/sessions/{sessionId}`
+- **规则**:`rule:BR-BA-001`、`rule:BR-BA-003`
+- **证据**:`documented`
diff --git a/ontology/events.md b/ontology/events.md
index a47fd24..b89d772 100644
--- a/ontology/events.md
+++ b/ontology/events.md
@@ -337,3 +337,35 @@
- **触发动作**:`action:view_workbench`
- **payload**:`storeId`、`userId`、`ts`
- **证据**:`gap`(A 期可不实现上报)
+
+## 智能预约助手 M0(Batch 0 · documented)
+
+### event:booking_agent_started
+
+智能预约 session 创建成功的首次事实。
+
+- **触发动作**:`action:create_booking_agent_session`
+- **幂等键**:`booking_agent_started:{sessionDbId}`
+- **BusinessEvent**:`storeId=session.storeId`、`aggregateType=booking_agent_session`、`aggregateId=session.id`
+- **metadata 白名单**:`entrySource`
+- **证据**:`documented`
+
+### event:booking_agent_draft_ready
+
+session 首次进入 `confirmable` 的事实,不表示预约已创建。
+
+- **触发动作**:`action:submit_booking_agent_message`
+- **幂等键**:`booking_agent_draft_ready:{sessionDbId}`
+- **metadata 白名单**:`inputModality`
+- **证据**:`documented`
+
+### event:booking_agent_fallback
+
+用户带草稿返回普通表单,或 LLM 失败后进入降级的首次事实。
+
+- **触发动作**:`action:handoff_booking_agent_draft`
+- **幂等键**:`booking_agent_fallback:{sessionDbId}`
+- **metadata 白名单**:`reason=user/llm_unavailable`
+- **证据**:`documented`
+
+M0 三个事件均禁止写入输入原文、转写文本、备注、宠物名、服务名、供应商 request ID 或错误原文;M0 不定义 `booking_agent_confirmed`。
diff --git a/ontology/objects.md b/ontology/objects.md
index d3b3dea..d14a48b 100644
--- a/ontology/objects.md
+++ b/ontology/objects.md
@@ -540,3 +540,30 @@ API 动态聚合字段:`pets`、`lastVisitAt`、`lastReportId`、`leadStatus`
- `entity:analytics_dashboard`(经营分析看板)— B 期;A 期仅工作台轻量指标
后续如进入实现,再补完整字段、动作与规则。
+
+---
+
+## 智能预约助手 M0(Batch 0 契约)
+
+## entity:booking_agent_session
+
+**智能预约短期会话**。它承载已登录 customer 在一个已选门店下的结构化草稿和六态状态机,不是预约,不持有任何预约写入权。
+
+| 字段 | 说明 |
+|------|------|
+| `id` | BIGINT 内部主键;用于低敏 `BusinessEvent.aggregateId` |
+| `session_id` | 唯一外部 UUID |
+| `customer_user_id` | 创建会话的 customer,只从登录上下文派生 |
+| `store_id` | 创建时必填并验证,会话期间固定 |
+| `status` | `collecting / proposing / confirmable / fallback / expired / cancelled` |
+| `draft_json` / `draft_version` | 最小已验证草稿与乐观版本 |
+| `entry_source` | M0 固定 `appointment_create` |
+| `input_modality` | `text / voice / mixed`,首次成功消息后派生 |
+| `expires_at` | 创建后 30 分钟,不滑动续期 |
+| `create_time / update_time` | 服务端审计时间 |
+
+- **不存储**:原始语音、转写原文、完整对话、提示词、模型原始请求/响应、`appointment_id`。
+- **归属系统**:Backend Core(计划 `bookingagent` 包)。
+- **文档**:`docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md`。
+- **证据**:`documented`(尚未创建 JPA 实体,机器图临时 `jpa:false`;实现批必须切换为真实 JPA 证据)。
+- **约束**:`rule:BR-BA-001` … `rule:BR-BA-005`。
diff --git a/ontology/relations.md b/ontology/relations.md
index 71e0cea..fc43463 100644
--- a/ontology/relations.md
+++ b/ontology/relations.md
@@ -187,3 +187,18 @@
| `business_event` PROJECTS_TO `customer_timeline` | 多条不可变事实经低敏白名单投影为时间线 |
| `view_store_customer_timeline` READS `store_customer` / `business_event` | 同店校验后分页读取主档与事实 |
| `admin_list_leads` READS `follow_up_task` | `/leads` 导航承载真实操作任务池,不直接编辑留资事实 |
+
+## 智能预约助手 M0 关系(Batch 0 · documented)
+
+| 关系 | 说明 |
+|------|------|
+| `user` HAS `booking_agent_session` | 一个 customer 可创建多个短期会话,每次读写校验 owner |
+| `store` HAS `booking_agent_session` | 每个会话在创建时固定一家门店 |
+| `create_booking_agent_session` CREATES `booking_agent_session` | 创建短期草稿会话 |
+| `submit_booking_agent_message` UPDATES `booking_agent_session` | 状态与草稿版本乐观更新 |
+| `transcribe_booking_agent_audio` READS `booking_agent_session` | 用 session 派生 owner/store 上下文,只返回可编辑文本 |
+| `handoff_booking_agent_draft` UPDATES `booking_agent_session` | 转为 fallback 并返回一次性草稿 |
+| `cancel_booking_agent_session` UPDATES `booking_agent_session` | 转为 cancelled |
+| `create_booking_agent_session` EMITS `booking_agent_started` | 创建成功的首次低敏事实 |
+| `submit_booking_agent_message` EMITS `booking_agent_draft_ready` | 首次进入 confirmable 时记录 |
+| `handoff_booking_agent_draft` EMITS `booking_agent_fallback` | 用户交接或 LLM 降级的首次事实 |
diff --git a/ontology/rules.md b/ontology/rules.md
index fd50fca..da541b7 100644
--- a/ontology/rules.md
+++ b/ontology/rules.md
@@ -479,3 +479,40 @@
- **代码**:`backend/src/main/java/com/petstore/service/BusinessEventService.java`、`AdminBusinessEventService.java`
- **测试**:`BusinessEventServiceTest`、`BusinessEventMigrationTest`、`AdminBusinessEventControllerTest`
- **证据**:`anchored`
+
+## 智能预约助手 M0(Batch 0 · documented)
+
+### rule:BR-BA-001
+
+**customer 与门店数据边界**:所有 endpoint 只允许已登录 customer;session 每次以 `sessionId + current.userId` 查询;他人与不存在 session 同样返回 `SESSION_NOT_FOUND`。`storeId` 只在创建时验证,后续不可覆盖。
+
+- **适用动作**:M0 全部五个动作
+- **证据**:`documented`
+
+### rule:BR-BA-002
+
+**模型非权威与结构强校验**:模型只返回通过 `booking-intent-v1` 的文本约束,未知字段拒绝。宠物、服务、日期时间和号源由后端确定性重新解析;模型不得产生 ID、价格、号源或预约状态事实。
+
+- **适用动作**:`action:submit_booking_agent_message`
+- **证据**:`documented`
+
+### rule:BR-BA-003
+
+**状态、版本、TTL 与无写路径**:M0 只允许六个会话状态,草稿更新必须校验 `draftVersion`;TTL 固定 30 分钟且不滑动续期,过期 24 小时后物理清理。M0 不注册 `/confirm`、不调用预约写服务、不修改 `Appointment`。
+
+- **适用动作**:创建、提交消息、草稿交接、结束会话
+- **证据**:`documented`
+
+### rule:BR-BA-004
+
+**语音、原文与日志最小化**:原始语音、当轮用户原文、完整对话、提示词和模型原始请求/响应不落库、不进事件、不进日志。音频最多 60 秒/3 MB,转写完立即释放,且必须经用户编辑/确认后才发消息。
+
+- **适用动作**:提交消息、语音转写、草稿交接
+- **证据**:`documented`
+
+### rule:BR-BA-005
+
+**默认关闭、限流与可退化**:`PETSTORE_BOOKING_AGENT_ENABLED` 默认 `false`。每 customer 10 分钟内最多创建 5 会话、30 条消息、10 段语音;模型/ASR/外部查询失败时保留已验证草稿并退回普通表单,不影响普通预约。单实例进程内限流只是 M0 临时边界,多实例前必须改为共享状态。
+
+- **适用动作**:创建会话、提交消息、语音转写、草稿交接
+- **证据**:`documented`
diff --git a/宠小它智能预约助手-产品与技术方案-v0.1.md b/宠小它智能预约助手-产品与技术方案-v0.1.md
index 20cde60..8acca51 100644
--- a/宠小它智能预约助手-产品与技术方案-v0.1.md
+++ b/宠小它智能预约助手-产品与技术方案-v0.1.md
@@ -1,7 +1,7 @@
# 宠小它智能预约助手:产品与技术方案 v0.1
> 日期:2026-08-02
-> 状态:**Draft / 待产品、架构、数据与隐私评审;尚未实现**
+> 状态:**M0 产品/架构/数据契约已冻结;尚未实现;真实供应商与生产隐私评审未完成**
> 目标用户:`customer` 宠主
> Workstream:`Core Booking Flow`
> 建议 Owner:`Product Design` + `Customer Experience FE` + `Backend Core`
@@ -15,12 +15,16 @@
- [模型与语音供应商选型](./智能预约助手-模型与语音供应商选型-2026-08-02.md)
- [M0 编码任务 brief](./智能预约助手-M0编码任务brief-2026-08-02.md)
+- [M0 契约与数据模型 ADR](./架构决策-智能预约助手M0契约与数据模型-2026-08-02.md)
+- [`booking-intent-v1` JSON Schema](./contracts/booking-intent-v1.schema.json)
+- [M0 OpenAPI 契约](./contracts/booking-agent-m0.openapi.yaml)
+- [M0 执行队列](./智能预约助手-M0执行队列-2026-08-02.md)
## 1. 结论
建议将该能力定义为 **「可执行的智能预约助手」**,而不是泛化客服聊天机器。
-它负责把宠主的自然语言转换为结构化预约草稿,通过现有 Petstore 预约域查询真实宠物、门店服务和可约时段,最后由用户明确确认后才创建 `Appointment`。
+它负责把宠主的自然语言转换为结构化预约草稿,通过现有 Petstore 预约域查询真实宠物、门店服务和可约时段。M0 只将草稿带入现有表单,仍由用户在普通表单中提交;对话内直接创建 `Appointment` 是 M1 候选能力。
**核心原则:模型负责「听懂人话」,Petstore 预约域负责「决定什么能约」。**
@@ -38,6 +42,8 @@
### 2.2 入口优先级
+下列为长期入口价值排序。M0 已冻结为仅从 `CustAppointmentCreate` 顶部次级入口进入,且必须先用现有控件选定门店。
+
1. **服务报告或历史预约:「跟助手约下次」**
上下文最完整,可在登录与归属校验后预填门店、宠物和上次服务。
2. **宠主首页:「一句话预约」**
@@ -108,6 +114,7 @@
- 普通预约仍保持「提交时登录 + guest 草稿恢复」的现有口径。
- 智能助手 MVP 在创建服务端会话前要求 customer 登录,因为它需要读取归属宠物和历史事实。
+- M0 在创建会话前还要求已选定有效门店;对话不负责选店,session 创建后不允许切店。
- 客户端可在登录前保存入口来源和非敏感意图,但不得在未登录状态调用宠物或历史数据工具。
- 会话过期后可将已解析的非敏感草稿回填到普通表单,不能自动续约或提交。
@@ -161,8 +168,10 @@ flowchart LR
H --> K[确认卡]
I --> K
J --> K
- K --> L[用户明确确认]
- L --> M[AppointmentService 最终复核与创建]
+ K --> L[M0 带入普通预约]
+ L --> M[CustAppointmentCreate 回填]
+ M --> N[现有预约域复核与创建]
+ K -. M1 另立 ADR 后 .-> O[对话内受控确认]
```
### 6.1 模型可以做的事
@@ -171,24 +180,24 @@ flowchart LR
- 提取非权威文本约束:宠物称呼、服务需求、相对日期、时间偏好、备注;
- 根据状态机的下一动作生成简短、可理解的回复。
-建议将模型输出限制为 JSON Schema:
+模型输出必须通过冻结的 [`booking-intent-v1`](./contracts/booking-intent-v1.schema.json) JSON Schema:
```json
{
- "intent": "book | modify | end | fallback",
- "draftPatch": {
- "petQuery": "球球",
- "serviceQuery": "洗澡",
- "dateExpression": "本周六",
- "timeWindow": { "start": "15:00", "end": null },
- "remark": null
- },
+ "schemaVersion": "booking-intent-v1",
+ "intent": "book",
+ "petQuery": "球球",
+ "serviceQuery": "洗澡",
+ "dateExpression": "本周六",
+ "timeWindow": { "start": "15:00", "end": null },
+ "remark": null,
+ "clearFields": [],
"ambiguities": [],
- "nextAction": "ask | resolve_context | search_slots | show_confirmation | fallback"
+ "nextAction": "resolve_context"
}
```
-`petQuery` 和 `serviceQuery` 只是搜索文本,不是业务 ID。所有 ID 必须由后端在当前登录上下文和门店数据范围中解析。
+`petQuery` 和 `serviceQuery` 只是搜索文本,不是业务 ID。所有 ID 必须由后端在当前登录上下文和门店数据范围中解析;未知字段和越界枚举直接拒绝,不从残缺输出猜测。
### 6.2 模型不可以做的事
@@ -209,7 +218,7 @@ flowchart LR
| `search_available_slots` | 只读 | 开始/结束时间、可约性 | 换日期或切换表单 |
| `resolve_previous_booking` | 只读 | 归属校验后的门店/宠物/服务 ID | 忽略历史预填并继续收集 |
-白名单中不包含写工具。最终创建由确认 endpoint 在模型调用之外完成。
+白名单中不包含写工具。M0 由普通表单在助手之外完成最终创建;M1 如增加确认 endpoint,也必须在模型调用之外进入预约域。
## 7. 预约草稿与数据模型提案
@@ -225,43 +234,41 @@ flowchart LR
| `timeWindow` | 模型提取 | 否 | 只是偏好约束 |
| `appointmentTime` | 真实号源选择 | 是 | 必须是精确的未来时间 |
| `remark` | 宠主原意摘要 | 否 | 限长、可编辑,不生成医疗结论 |
-| `sourceAppointmentId` | 已验证复约入口 | 是 | 可空,必须归属当前 customer |
| `draftVersion` | 服务端 | 是 | 每次草稿变更递增,确认时防旧版提交 |
确认时不直接信任草稿中的展示快照。后端需重新加载 Pet、ServiceType、Store 和容量事实,再由现有预约域生成 `petName`、`petType`、`serviceType`和 `durationMinutes` 快照。
### 7.2 候选会话表
-长期建议新增 `t_booking_agent_session`,不将对话状态塞入 `t_appointment`。下表是覆盖 M1 直接确认能力的完整候选形态;M0 实际字段以配套编码 brief 为准。
+新增 `t_booking_agent_session`,不将对话状态塞入 `t_appointment`。M0 字段已由 ADR 冻结:
| 字段 | 语义 |
|---|---|
-| `session_id` | 不可预测会话 ID,主键 |
+| `id` | BIGINT 自增内部主键 |
+| `session_id` | VARCHAR(36) 唯一外部 UUID,不暴露内部主键 |
| `customer_user_id` | 从登录上下文派生 |
-| `store_id` | 会话数据范围 |
-| `source_appointment_id` | 复约上下文,可空 |
-| `status` | 仅使用第 5 节对话状态 |
+| `store_id` | NOT NULL,会话固定数据范围 |
+| `status` | M0 六个状态之一 |
| `draft_json` | 最小结构化草稿,不存原始语音 |
| `draft_version` | 乐观版本号 |
-| `entry_source` | `appointment_create` / `home` / `appointment_history` / `report_history` / `pet_profile` |
+| `entry_source` | M0 固定 `appointment_create` |
| `input_modality` | `text` / `voice` / `mixed` |
-| `appointment_id` | M1 成功后关联的真实预约,可空;M0 不增加 |
-| `expires_at` | 会话过期时间 |
+| `expires_at` | 创建后 30 分钟,不滑动续期 |
| `create_time` / `update_time` | 审计时间 |
数据建议:
- 以 `(customer_user_id, status, expires_at)` 建立查询索引;
-- M1 增加 `appointment_id` 可空唯一关联;
-- M1 确认时对 session 行加悲观锁;如已是 `booked`,重复请求直接返回同一 `Appointment`;
-- MVP 会话默认 30 分钟过期,过期不等于删除预约;
+- 以 `(status, expires_at)` 支持过期标记和清理;
+- 会话过期 24 小时后物理删除,M0 不增加 `deleted`;
+- M0 不增加 `source_appointment_id` 或 `appointment_id`;
- 不新增 `t_booking_agent_turn` 原文表;模型每轮以当前草稿和当次输入工作,避免默认持久化完整对话。
-该表、索引、过期清理和迁移顺序均是 **待 Data Model 评审的提案**,不代表当前生产库已存在。
+上述是 **M0 已评审契约**,不代表当前生产库已存在;迁移实现 diff 仍必须经 Data Model Review。
### 7.3 预约来源与事件
-不改变现有 `BusinessEvent.source=customer/admin`的操作人语义。建议在 `appointment_created` 的 `metadata_json` 增加独立维度:
+不改变现有 `BusinessEvent.source=customer/admin`的操作人语义。长期可在 `appointment_created` 的 `metadata_json` 评审独立维度:
```json
{
@@ -273,119 +280,52 @@ flowchart LR
`bookingChannel` 候选值:`form`、`agent_draft`、`agent_text`、`agent_voice`、`admin`、`follow_up`。`agent_draft` 只表示智能助手提供了已验证草稿;`agent_text` 和 `agent_voice` 保留给 M1 对话内直接创建路径。初始 M0 不修改 `appointment_created` 事件,如需归因表单提交,必须另行评审服务端验证的一次性草稿交接,不接受客户端自由上报渠道。不建议 MVP 为此修改 `Appointment` 主表。
-候选新事件:
+冻结的 M0 新事件:
- `booking_agent_started`;
- `booking_agent_draft_ready`;
-- `booking_agent_fallback`;
-- `booking_agent_confirmed`(M1)。
+- `booking_agent_fallback`。
-这些事件只用于漏斗和运行分析;只有真实创建成功的 `appointment_created` 才计为预约。编码前需将候选对象、动作、事件和规则同步到 `docs/ontology/` 及 `graph/ontology.jsonl`。
+这些事件只用于漏斗和运行分析;只有真实创建成功的 `appointment_created` 才计为预约。M0 不增加 `booking_agent_confirmed`,不修改现有 `appointment_created`。
-## 8. API 契约草案
+## 8. M0 API 冻结契约
-本节是评审草案,尚未实现。响应沿用当前 `{ code, message, bizCode, data }` 业务外壳,所有 endpoint 都要求 customer session token。
+契约已冻结、尚未实现。唯一机读权威是 [M0 OpenAPI](./contracts/booking-agent-m0.openapi.yaml);响应沿用当前 `{ code, message, bizCode, data }` 外壳,除鉴权拦截的 HTTP 401 外,客户端以 body `code` 为业务结果。本功能不顺带重构全局响应语义。
-### 8.1 创建会话
+### 8.1 Endpoint
-`POST /api/booking-agent/sessions`
-
-```json
-{
- "entrySource": "home",
- "storeId": 1,
- "sourceAppointmentId": null
-}
-```
-
-规则:
-
-- 不接受 `customerUserId`,必须从 `CurrentUserContext` 派生;
-- `sourceAppointmentId` 非空时必须归属当前 customer;
-- `storeId` 与源预约冲突时拒绝,不静默覆盖;
-- 返回 `sessionId`、`status`、`draft`、`assistantMessage`、`expiresAt` 和 `draftVersion`。
-
-### 8.2 提交文字或语音转写结果
-
-`POST /api/booking-agent/sessions/{sessionId}/messages`
-
-```json
-{
- "inputType": "text",
- "text": "周六下午给球球洗澡,三点以后都行"
-}
-```
-
-建议响应 `data`:
-
-```json
-{
- "sessionId": "",
- "status": "proposing",
- "assistantMessage": "本周六有 3 个符合的时段",
- "draft": {},
- "slotOptions": [
- { "startTime": "2026-08-08T15:00:00", "endTime": "2026-08-08T16:00:00" }
- ],
- "quickReplies": ["15:00", "16:30", "17:00", "换一天"],
- "confirmable": false,
- "draftVersion": 2
-}
-```
-
-客户端只展示可理解错误,不展示模型原始输出、提示词、堆栈或供应商错误。
-
-### 8.3 语音转写
-
-`POST /api/booking-agent/transcriptions`
-
-- `multipart/form-data`,仅接受允许的音频 MIME 和扩展名;
-- M0 单段不超过 60 秒、3 MB,与供应商选型和编码 brief 保持一致;
-- 后端代理调用语音服务,不向客户端下发供应商密钥;
-- 语音只用于当次转写,不进入通用报告媒体存储,不持久化原始录音;
-- 返回可编辑 `text` 和脱敏 `requestId`,宠主可修改识别结果再发送。
-
-### 8.4 确认预约(M1,M0 不实现)
-
-`POST /api/booking-agent/sessions/{sessionId}/confirm`
-
-```json
-{
- "draftVersion": 3
-}
-```
-
-确认 endpoint 必须:
-
-1. 从 session token 重新派生 customer;
-2. 对 `t_booking_agent_session` 行加锁;
-3. 校验 `draftVersion`、会话状态和过期时间;
-4. 重新校验宠物归属、门店服务和号源;
-5. 调用现有 `AppointmentService.createBooking`,保留门店行锁和连续容量桶判定;
-6. 记录 `appointment_created` 及助手漏斗事件;
-7. 将 session 更新为 `booked` 并关联 `appointmentId`。
-
-并发或重试时,同一 session 只能创建一个预约;后续重复确认返回已创建的同一预约。
-
-### 8.5 返回草稿到普通表单
-
-`POST /api/booking-agent/sessions/{sessionId}/fallback`
-
-返回经后端验证的可回填字段,客户端导航到现有 `CustAppointmentCreate`。不返回供应商上下文、提示词、置信度或内部调试字段。
-
-### 8.6 业务码草案
-
-| `bizCode` | 用户语义 | 前端处理 |
+| Method | Endpoint | M0 语义 |
|---|---|---|
-| `AGENT_SESSION_NOT_FOUND` | 会话不存在或不属于当前宠主 | 返回预约入口 |
-| `AGENT_SESSION_EXPIRED` | 会话已过期 | 新建会话或切表单 |
-| `AGENT_INPUT_INVALID` | 输入为空或超限 | 允许重新输入 |
-| `AGENT_UNAVAILABLE` | 模型超时或输出校验失败 | 保留草稿并切表单 |
-| `ASR_UNAVAILABLE` | 语音转写失败 | 重试或改用文字 |
-| `DRAFT_VERSION_CONFLICT` | 确认的不是最新草稿 | 刷新确认卡 |
-| `PET_CUSTOMER_MISMATCH` | 宠物归属校验失败 | 重新选宠物 |
-| `SERVICE_TYPE_INVALID` | 服务不属于门店或已下线 | 重新选服务 |
-| `CAPACITY_FULL` | 确认时号源已变化 | 立即查询新候选时段 |
+| `POST` | `/api/booking-agent/sessions` | 以已选 `storeId` 创建 customer 会话;`entrySource` 由服务端固定 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/messages` | 携带 `draftVersion`、`inputType` 和文本更新草稿 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/transcriptions` | 在本人 session 下转写不超过 60 秒/3 MB 的短音频,仅返回可编辑文本 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/fallback` | 携带 `draftVersion`,返回服务端验证的一次性回填草稿 |
+| `DELETE` | `/api/booking-agent/sessions/{sessionId}` | 将本人非终态会话标记为 `cancelled` |
+
+所有 endpoint 只允许已登录 `customer`;session 每次用 `sessionId + current.userId` 查询,他人 session 和不存在 session 统一为 `SESSION_NOT_FOUND`。创建后 `storeId` 固定,消息和 fallback 必须进行草稿版本检查。
+
+M0 不注册 `/confirm`,不调用 `AppointmentService.createBooking`;M1 必须新立 ADR 审查写路径、行锁、幂等和事件归因。
+
+### 8.2 业务码
+
+| body code | `bizCode` | 语义 |
+|---|---|---|
+| 401 | `UNAUTHENTICATED` | session token 缺失或失效 |
+| 403 | `FORBIDDEN` | 非 customer 调用 |
+| 503 | `AGENT_DISABLED` | 功能开关关闭 |
+| 404 | `STORE_NOT_FOUND` | 创建时目标门店无效 |
+| 404 | `SESSION_NOT_FOUND` | session 不存在或不属于当前 customer |
+| 410 | `SESSION_EXPIRED` | 会话 TTL 到期 |
+| 409 | `SESSION_TERMINAL` | 会话已 fallback/cancelled |
+| 409 | `DRAFT_VERSION_CONFLICT` | 客户端草稿版本过旧 |
+| 400 | `INVALID_INPUT` | 空文本、超长或请求结构无效 |
+| 400 | `INVALID_AUDIO` | 空音频、格式/MIME/文件头不匹配或时长非法 |
+| 413 | `AUDIO_TOO_LARGE` | 超过 3 MB |
+| 429 | `RATE_LIMITED` | customer 请求频率超限 |
+| 503 | `AGENT_UNAVAILABLE` | LLM 超时、无效 JSON/schema 或外部失败 |
+| 503 | `ASR_UNAVAILABLE` | ASR 超时或外部失败 |
+
+宠物/服务歧义、无号源和未选时段是正常 200 会话响应,不用异常码表达。
## 9. 语音与模型集成边界
@@ -409,7 +349,7 @@ flowchart LR
- 语音失败:保留本地输入状态,提供「重新说」和「改用文字」。
- 模型失败:不自动重复多次调用;保留已验证草稿并导向普通表单。
- 工具查询失败:展示业务错误,不让模型根据记忆补齐结果。
-- 最终号源冲突:会话进入 `needs_reselection`,不创建失败预约或假成功记录。
+- M1 最终号源冲突:会话进入 `needs_reselection`,不创建失败预约或假成功记录。M0 只查询候选号源并回填表单。
## 10. 安全、隐私与可观测性
@@ -476,7 +416,7 @@ flowchart LR
### M1:受控确认创建
-- 增加 session 表、确认 endpoint、行锁和幂等返回;
+- 在 M0 session 表上增加 `appointment_id`、确认 endpoint、行锁和幂等返回;
- 确认时重新校验权限、宠物归属、服务和容量;
- 补齐助手漏斗事件和 `bookingChannel`;
- 通过硬性安全验收后,再允许确认卡直接创建预约。
@@ -492,19 +432,21 @@ flowchart LR
### 13.1 硬性门禁
-- [ ] 未点击「确认预约」前不创建 `Appointment`。
+- [ ] M0 不存在助手 `/confirm` endpoint,不调用预约写服务。
- [ ] 助手不会返回门店不存在的服务或后端未返回的号源。
-- [ ] 确认时复核服务时长、连续容量桶和门店锁,不产生超卖。
-- [ ] 同一 session 并发或重复确认只有一个 `Appointment`。
+- [ ] 草稿只使用当前 customer 的宠物、session 门店服务和后端真实号源。
+- [ ] 门店在会话前选定,session 创建后不能跨店更改。
- [ ] A customer 不能查询或选择 B customer 的宠物、历史预约或 session。
- [ ] 不将手机号、完整 `report_token`、session token 或密钥发送给模型或写入日志。
- [ ] 原始语音转写后立即释放,不出现在通用上传目录或报告媒体中。
- [ ] 模型、语音或工具失败时能保留已验证草稿并切换普通表单。
- [ ] `Appointment.status` 仍只有 `new`、`doing`、`done`、`cancel`。
-- [ ] 助手生成的真实预约有 `appointment_created` 事件和可统计 `bookingChannel`。
+- [ ] 仅记录 M0 三个漏斗事件,不修改 `appointment_created` 事件语义。
### 13.2 价值验证建议
+首批 M0 先观察 `draft_ready / started`、`fallback / started`、语音转写修改率和普通表单回填成功率。下列「助手到真实预约」指标只能在有可验证的服务端草稿归因后使用,不接受客户端自由上报。
+
首批 20 个真实 session 用于发现语料和交互问题,不立即下商业化结论。稳定后可对 20~50 个老客 session 使用以下建议门槛:
- 助手启动到真实预约成功率 ≥ 70%;
@@ -515,22 +457,27 @@ flowchart LR
上述价值门槛是试点假设,应在首批真实数据后与普通表单基线一起复核;不得用演示会话、测试预约或未履约预约代替真实样本。
-## 14. 开发前待冻结决策
+## 14. M0 已冻结决策与生产前缺口
| ID | 决策 | 建议默认 | 责任人 |
|---|---|---|---|
-| D1 | M0 首发入口 | `CustAppointmentCreate` 顶部次级入口;历史预约、报告和首页入口后续评估 | Product Design |
+| D1 | M0 首发入口 | `CustAppointmentCreate` 顶部次级入口;先选门店,再登录/创建 session | Product Design |
| D2 | 是否支持匿名助手 | MVP 不支持,普通表单继续支持 guest 草稿 | Product Design + System Architect |
| D3 | M0 是否直接创建预约 | 否,先回填现有表单 | Product Design + Backend Core |
-| D4 | 模型与语音供应商 | M0 推荐阿里云百炼北京 `qwen3-asr-flash-2026-02-10 + qwen-plus-2025-12-01`,腾讯 ASR 作对照;生产前仍需语料和隐私复核 | System Architect + Backend Ops |
+| D4 | 模型与语音供应商 | 开发适配目标已选;生产开启仍受语料对照、数据区域和隐私复核阻塞 | System Architect + Backend Ops |
| D5 | 原文保留 | 默认不持久化对话原文,不持久化原始语音 | Access / Privacy Review |
-| D6 | session TTL | MVP 30 分钟,编码前由 Data Model 冻结 | Data Model |
+| D6 | session TTL | 创建后固定 30 分钟,不滑动续期;过期 24 小时后物理删除 | Data Model |
| D7 | 助手对话风格 | 简短、一次一个关键问题,不模拟人格客服 | Product Design |
| D8 | 试点启动时机 | RC6 基础链路已用真实门店跑通并建立普通表单基线后 | 主控 PM |
-## 15. 实施任务队列草案
+## 15. 实施任务队列
-以下任务默认为 `Backlog`,不因本文档创建而自动进入开发。
+当前可执行状态、owner、锁、依赖和验收口径只以 [M0 执行队列](./智能预约助手-M0执行队列-2026-08-02.md) 为准。其中 Batch 1–3 已具备开发边界;真实供应商和生产开启仍为 `Blocked`。
+
+
+已被独立执行队列取代的历史草案(仅供审计,不执行)
+
+以下 BA-01~BA-07 是契约冻结前的原始 Backlog,不再表示当前任务状态。
### BA-01 冻结智能预约产品与交互契约
@@ -707,12 +654,14 @@ Data Model Review Required: Yes
Access / Privacy Review Required: Yes
```
+
+
## 16. 开发与验证清单
实现时至少需要:
1. 更新 `docs/ontology/objects.md`、`actions.md`、`events.md`、`rules.md`、`relations.md` 和 `graph/ontology.jsonl`;
-2. 后端覆盖模型无效输出、跨 customer/跨店、宠物归属、服务下线、号源冲突、重复确认和 session 过期;
+2. 后端覆盖模型无效输出、跨 customer/跨店、宠物归属、服务下线、无号源、`draftVersion` 冲突、session 过期和「不存在助手写路径」;
3. 前端覆盖语音失败、模型失败、空输入、草稿修改、无号源、提交 loading 和普通表单降级;
4. 生产前检查供应商密钥、允许数据区域、超时、频控、日志脱敏和降级开关;
5. 以普通预约为基线,不用智能助手取代现有真实门店试点指标。
@@ -737,6 +686,6 @@ git -C /Users/apple/_src/petstore/docs diff --check
| 门店服务 | 服务项目具有名称和 `durationMinutes` | 智能助手只从真实列表中匹配 |
| 可约时段 | `/api/appointment/available-slots` 按时长和容量返回 | 可复用领域服务,不由模型计算 |
| 预约创建 | `AppointmentService.createBooking` 复核宠物、服务、时间和容量 | M1 确认 endpoint 的唯一写入权威 |
-| 业务事件 | `appointment_created` 已持久化 | 增加独立 `bookingChannel` 和助手漏斗事件 |
+| 业务事件 | `appointment_created` 已持久化 | M0 只增加三个助手漏斗事件;`bookingChannel` 与直接创建归因留待 M1 评审 |
本方案是对现有预约入口的语义化扩展,不建立第二套预约、容量或身份系统。
diff --git a/智能预约助手-M0执行队列-2026-08-02.md b/智能预约助手-M0执行队列-2026-08-02.md
new file mode 100644
index 0000000..2a70134
--- /dev/null
+++ b/智能预约助手-M0执行队列-2026-08-02.md
@@ -0,0 +1,181 @@
+# 智能预约助手 M0 执行队列
+
+> 日期:2026-08-02
+> 状态:**Batch 0 contract frozen / coding not started**
+> RC Included:`No`
+> 当前优先级说明:不抢占 `phase2-pilot-rc6` 真实门店基线问题;按独立 feature flag 开发
+
+**冻结输入**
+
+- [架构决策:M0 契约与数据模型](./架构决策-智能预约助手M0契约与数据模型-2026-08-02.md)
+- [`booking-intent-v1` JSON Schema](./contracts/booking-intent-v1.schema.json)
+- [M0 OpenAPI 契约](./contracts/booking-agent-m0.openapi.yaml)
+- [M0 编码任务 brief](./智能预约助手-M0编码任务brief-2026-08-02.md)
+
+## 队列摘要
+
+| Task Name | Owner Agent | Status | Depends On |
+|---|---|---|---|
+| 实现智能预约供应商适配与契约测试 | Backend Core | Ready | Batch 0 冻结资产 |
+| 实现智能预约会话与只读预约编排 | Backend Core | Ready | Batch 0 冻结资产;provider interface |
+| 实现宠主一句话预约与草稿回填 | Customer Experience FE | Ready | OpenAPI;可先用 mock |
+| 验收智能预约 M0 草稿链路 | Core Flow QA | Ready | 可先写 fixture/smoke;执行依赖前三项 |
+| 完成真实供应商对照与生产隐私复核 | Backend Ops | Blocked | 云账号、密钥、授权语音和上线授权 |
+
+## Task:实现智能预约供应商适配与契约测试
+
+| 字段 | 值 |
+|---|---|
+| Task Name | 实现智能预约供应商适配与契约测试 |
+| Role | `Backend Core` |
+| Owner Agent | `Backend Core` |
+| Paired QA | `Core Flow QA` |
+| Workstream | `Core Booking Flow` |
+| Track | `Coding` |
+| Status | `Ready` |
+| Priority | `P0` |
+| Repo/Path | `backend/src/main/java/com/petstore/bookingagent/provider`、对应 config/test |
+| Service Lock | `backend:booking-agent-provider` |
+| Depends On | M0 ADR、JSON Schema、供应商选型 |
+| Acceptance | provider interface + 本地 HTTP stub;30 条文字 fixture;无效 JSON/schema 降级;未配置密钥时应用正常启动 |
+| RC Included | `No` |
+| Due Date | `TBD` |
+| Output Link | `TBD — implementation commit` |
+| Blocker Owner | `None`(真实 provider smoke 不在本任务验收内) |
+| Commit / RC | `Not Frozen` |
+| Architecture Review Required | `Yes — completed by M0 ADR` |
+| Data Model Review Required | `No` |
+| Access / Privacy Review Required | `Yes — development boundary completed; live smoke pending` |
+
+额外门禁:
+
+- 使用 JDK `HttpClient` 和可注入 timeout;
+- 测试不得访问公网;
+- prompt、原文、Authorization、API Key 和模型原始响应不进日志;
+- `booking-intent-v1` 未通过完整校验时不返回部分 patch。
+
+## Task:实现智能预约会话与只读预约编排
+
+| 字段 | 值 |
+|---|---|
+| Task Name | 实现智能预约会话与只读预约编排 |
+| Role | `Backend Core` |
+| Owner Agent | `Backend Core` |
+| Paired QA | `Core Flow QA` |
+| Workstream | `Core Booking Flow` |
+| Track | `Coding` |
+| Status | `Ready` |
+| 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` |
+| Depends On | M0 ADR、OpenAPI、provider interface |
+| Acceptance | 五个 endpoint;customer 归属;六态状态机;draftVersion;30 分钟 TTL/24 小时清理;只读宠物/服务/容量;三个低敏事件;不存在 `/confirm` |
+| RC Included | `No` |
+| Due Date | `TBD` |
+| Output Link | `TBD — implementation commit` |
+| Blocker Owner | `None` |
+| Commit / RC | `Not Frozen` |
+| Architecture Review Required | `Yes — completed by M0 ADR` |
+| Data Model Review Required | `Yes — proposal completed; implementation diff requires Data Model review` |
+| Access / Privacy Review Required | `Yes — development boundary completed` |
+
+额外门禁:
+
+- `storeId` 创建时必填,后续只从 session 读取;
+- session 必须用 `sessionId + current.userId` 查询;
+- 整套测试验证不调用 `AppointmentService.createBooking` / `AppointmentMapper.save`;
+- 迁移包含索引、状态验证和 TTL 清理验证查询;
+- 实现时同步把对应本体证据从 `documented` 更新为 `anchored`。
+
+## Task:实现宠主一句话预约与草稿回填
+
+| 字段 | 值 |
+|---|---|
+| Task Name | 实现宠主一句话预约与草稿回填 |
+| Role | `Customer Experience Frontend` |
+| Owner Agent | `Customer Experience FE` |
+| Paired QA | `Core Flow QA` |
+| Workstream | `Core Booking Flow` |
+| Track | `Coding` |
+| Status | `Ready` |
+| Priority | `P0` |
+| Repo/Path | `frontend/src/pages/appointment`、`frontend/src/api/index.js`、`frontend/src/utils/bookingAgentDraft.js`、`frontend/src/pages.json` |
+| Service Lock | `frontend:customer-booking-agent+shared-api` |
+| Depends On | M0 OpenAPI;后端未完成前允许契约 mock |
+| Acceptance | 先选门店;customer 入口;文字链路;按住说话与隐私告知;转写可编辑;选择芯片;草稿卡;一次性回填;失败不影响普通表单 |
+| RC Included | `No` |
+| Due Date | `TBD` |
+| Output Link | `TBD — implementation commit` |
+| Blocker Owner | `None`(真机录音最终验收依赖测试设备) |
+| Commit / RC | `Not Frozen` |
+| Architecture Review Required | `Yes — completed by M0 ADR` |
+| Data Model Review Required | `No` |
+| Access / Privacy Review Required | `Yes — 语音首次告知与权限拒绝降级必须 review` |
+
+额外门禁:
+
+- 未选门店时先使用现有门店控件,不创建 agent session;
+- 转写不得自动调用 messages;
+- 对话消息不写 Storage,一次性 handoff 草稿消费后删除;
+- `frontend/src/api/index.js` 写入前通知 Backend Core 并持有 `shared-api` 锁;
+- H5 不具备录音能力时只隐藏语音按钮,文字主链路仍可用。
+
+## Task:验收智能预约 M0 草稿链路
+
+| 字段 | 值 |
+|---|---|
+| Task Name | 验收智能预约 M0 草稿链路 |
+| Role | `Core Flow QA` |
+| Owner Agent | `Core Flow QA` |
+| Paired QA | `Backend Core` + `Customer Experience FE` |
+| Workstream | `Core Booking Flow` |
+| Track | `QA` |
+| Status | `Ready` |
+| Priority | `P0` |
+| Repo/Path | `docs/qa-reports`、必要 smoke 脚本;不改业务代码 |
+| Service Lock | `docs:qa-booking-agent-m0` |
+| Depends On | 立即可写 fixture/smoke;执行依赖三个实现任务 |
+| Acceptance | 权限/跨 customer;状态与版本;时间解析;真实号源;语音限制;日志脱敏;无写路径;普通预约回归;H5/mp-weixin 构建 |
+| RC Included | `No` |
+| Due Date | `TBD` |
+| Output Link | `TBD — QA report` |
+| Blocker Owner | `None` |
+| Commit / RC | `Not Frozen` |
+| Architecture Review Required | `No` |
+| Data Model Review Required | `No` |
+| Access / Privacy Review Required | `Yes — 验收日志与语音告知` |
+
+QA 必须提供反证:
+
+- 后端没有 `/api/booking-agent/sessions/{sessionId}/confirm`;
+- M0 测试执行期间没有 `AppointmentMapper.save`;
+- 他人/不存在 session 返回同一 `SESSION_NOT_FOUND`;
+- provider 超时、无效 JSON、ASR 失败均能回到普通预约;
+- 原始语音、当轮原文、模型响应和密钥不出现在库、日志、截图或 fixture。
+
+## Task:完成真实供应商对照与生产隐私复核
+
+| 字段 | 值 |
+|---|---|
+| Task Name | 完成真实供应商对照与生产隐私复核 |
+| Role | `Backend Ops` |
+| Owner Agent | `Backend Ops` |
+| Paired QA | `Core Flow QA` |
+| Workstream | `Release` |
+| Track | `Review` |
+| Status | `Blocked` |
+| Priority | `P1` |
+| Repo/Path | 本地密钥存储、低敏 smoke 证据、`docs/qa-reports`;不改业务逻辑 |
+| Service Lock | `ops:booking-agent-provider-smoke` |
+| Depends On | 阿里云北京 Workspace/API Key;20 条已授权语音;隐私告知;实现任务完成 |
+| Acceptance | 30 条文字/20 条语音门槛;腾讯 ASR 对照;p50/p95;费用告警;日志审计;默认 flag 关闭 |
+| RC Included | `No` |
+| Due Date | `TBD` |
+| Output Link | `TBD — provider evaluation report` |
+| Blocker Owner | `Product Owner / Access & Privacy / Cloud Account Owner` |
+| Commit / RC | `Not Frozen` |
+| Architecture Review Required | `Yes — supplier result may change baseline` |
+| Data Model Review Required | `No` |
+| Access / Privacy Review Required | `Yes — pending` |
+
+阻塞条件不会阻塞前三项本地开发。真实 Workspace ID 和 API Key 只从密钥存储读取,不进入任务评论或 Git。
diff --git a/智能预约助手-M0编码任务brief-2026-08-02.md b/智能预约助手-M0编码任务brief-2026-08-02.md
index 1492272..702cac4 100644
--- a/智能预约助手-M0编码任务brief-2026-08-02.md
+++ b/智能预约助手-M0编码任务brief-2026-08-02.md
@@ -1,32 +1,38 @@
# 智能预约助手 M0 编码任务 brief
> 日期:2026-08-02
-> 状态:**Ready for Review / 尚未进入编码**
+> 状态:**Ready / Batch 0 契约已冻结,尚未进入编码**
> 目标:宠主用文字或语音生成可验证预约草稿,回填现有表单,不由智能层创建 `Appointment`
> Workstream:`Core Booking Flow`
> Owner Agent:`Backend Core` + `Customer Experience FE`
> Paired QA:`Core Flow QA`
> Service Lock:`backend:booking-agent-session+read-tools` + `frontend:customer-booking-agent+shared-api`
-> Data Model Review Required:`Yes`
-> Access / Privacy Review Required:`Yes`
+> Architecture Review Required:`Yes — completed by M0 ADR`
+> Data Model Review Required:`Yes — proposal completed; implementation diff requires review`
+> Access / Privacy Review Required:`Yes — development boundary completed; live provider/production pending`
> RC Included:`No`
**需求来源**
- [智能预约助手产品与技术方案 v0.1](./宠小它智能预约助手-产品与技术方案-v0.1.md)
- [模型与语音供应商选型](./智能预约助手-模型与语音供应商选型-2026-08-02.md)
+- [M0 契约与数据模型 ADR](./架构决策-智能预约助手M0契约与数据模型-2026-08-02.md)
+- [`booking-intent-v1` JSON Schema](./contracts/booking-intent-v1.schema.json)
+- [M0 OpenAPI 契约](./contracts/booking-agent-m0.openapi.yaml)
+- [M0 执行队列](./智能预约助手-M0执行队列-2026-08-02.md)
## 1. M0 交付结果
-宠主在已登录状态进入「一句话预约」,输入文字或按住说话。系统依次:
+宠主在普通预约页先选定门店,并在已登录状态进入「一句话预约」。系统依次:
-1. 转写语音(文字输入跳过);
-2. 提取宠物查询词、服务查询词、日期表达、时间偏好和备注;
-3. 在当前 customer 和门店范围内解析真实 Pet 与 ServiceType;
-4. 调用现有容量服务获取真实号源;
-5. 展示可修改草稿卡;
-6. 宠主点击「带入普通预约」后,回填现有 `CustAppointmentCreate`;
-7. 宠主仍在现有表单上点击「提交预约」。
+1. 使用已选 `storeId` 创建会话;
+2. 转写语音(文字输入跳过);
+3. 提取宠物查询词、服务查询词、日期表达、时间偏好和备注;
+4. 在当前 customer 和 session 门店范围内解析真实 Pet 与 ServiceType;
+5. 调用现有容量服务获取真实号源;
+6. 展示可修改草稿卡;
+7. 宠主点击「带入普通预约」后,回填现有 `CustAppointmentCreate`;
+8. 宠主仍在现有表单上点击「提交预约」。
M0 成功的判定是 **「草稿真实、回填正确、普通预约未受影响」**,不是对话内创建预约。
@@ -47,21 +53,24 @@ M0 成功的判定是 **「草稿真实、回填正确、普通预约未受影
```mermaid
flowchart TD
A[宠主预约页] --> B[点击一句话预约]
- B --> C{已登录 customer}
- C -- 否 --> D[登录并保留返回路由]
- D --> E[创建助手会话]
+ B --> C{已选有效门店}
+ C -- 否 --> D[使用现有控件选择门店]
+ D --> E{已登录 customer}
C -- 是 --> E
- E --> F[文字或语音输入]
- F --> G[结构化意图提取]
- G --> H[服务端解析真实宠物和服务]
- H --> I[查询真实号源]
- I --> J{草稿完整}
- J -- 否 --> K[询问一个关键缺失项]
- K --> F
- J -- 是 --> L[展示草稿卡]
- L --> M[带入普通预约]
- M --> N[CustAppointmentCreate 回填]
- N --> O[用户在现有表单提交]
+ E -- 否 --> F[登录并保留返回路由和门店]
+ F --> G[创建助手会话]
+ E -- 是 --> G
+ G --> H[文字或语音输入]
+ H --> I[结构化意图提取]
+ I --> J[服务端解析真实宠物和服务]
+ J --> K[查询真实号源]
+ K --> L{草稿完整}
+ L -- 否 --> M[询问一个关键缺失项]
+ M --> H
+ L -- 是 --> N[展示草稿卡]
+ N --> O[带入普通预约]
+ O --> P[CustAppointmentCreate 回填]
+ P --> Q[用户在现有表单提交]
```
### 3.1 入口
@@ -72,6 +81,8 @@ M0 只在宠主的 `CustAppointmentCreate` 顶部增加一个次级入口:
该入口不取代当前表单和主提交按钮,不改宠主首页主 CTA。历史预约、报告页和宠物档案深链放到后续迭代。
+M0 不在对话内选择门店。未选门店时先使用现有表单控件;创建 session 后 `storeId` 固定,切店需结束会话并重新创建。
+
### 3.2 对话风格
- 回复简短,一次只问一个关键缺失项;
@@ -159,22 +170,23 @@ M0 只允许:
M0 字段:
-- `session_id` VARCHAR(64) 主键;
+- `id` BIGINT 自增主键;
+- `session_id` VARCHAR(36) 唯一外部 UUID;
- `customer_user_id` BIGINT NOT NULL;
-- `store_id` BIGINT NULL;
-- `status` VARCHAR(32) NOT NULL;
+- `store_id` BIGINT NOT NULL;
+- `status` VARCHAR(24) NOT NULL;
- `draft_json` TEXT NOT NULL;
- `draft_version` INT NOT NULL;
- `entry_source` VARCHAR(32) NOT NULL;
- `input_modality` VARCHAR(16) NULL;
- `expires_at` DATETIME NOT NULL;
-- `create_time` / `update_time`;
-- `deleted` TINYINT(1) NOT NULL DEFAULT 0。
+- `create_time` / `update_time`。
索引:
+- `UNIQUE (session_id)`;
- `(customer_user_id, status, expires_at)`;
-- `(expires_at, deleted)` 用于过期清理。
+- `(status, expires_at)` 用于过期和物理清理。
M0 不增加 `appointment_id`,也不建立 `t_booking_agent_turn`。`appointment_id` 唯一关联在 M1 幂等写入评审时再增加,避免 M0 数据模型暗示已具备直接创建能力。
@@ -182,6 +194,8 @@ M0 的 `entry_source` 只允许 `appointment_create`;历史预约、报告、
`draft_json` 只保存已验证 ID、时间约束和必要备注;不保存每轮用户原文、提示词或模型原始响应。
+TTL 固定为创建后 30 分钟,不滑动续期;过期 24 小时后物理删除。M0 不增加 `deleted`,详细迁移和清理口径以 ADR 为准。
+
### 4.5 API 契约
M0 只实现:
@@ -190,12 +204,14 @@ M0 只实现:
|---|---|---|
| `POST` | `/api/booking-agent/sessions` | 创建已登录 customer 会话 |
| `POST` | `/api/booking-agent/sessions/{sessionId}/messages` | 提交文字/语音转写文本,刷新草稿 |
-| `POST` | `/api/booking-agent/transcriptions` | 上传短音频并获取可编辑转写 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/transcriptions` | 在本人 session 下上传短音频并获取可编辑转写 |
| `POST` | `/api/booking-agent/sessions/{sessionId}/fallback` | 返回可回填普通表单的已验证草稿 |
| `DELETE` | `/api/booking-agent/sessions/{sessionId}` | 将本人会话标记为 `cancelled` |
所有 endpoint 都要求 `CurrentUserContext.require()` 且 role 为 `customer`。每次读写 session 都以 `sessionId + current.userId` 查询;他人 session 和不存在 session 均返回统一 404 语义,避免泄漏 session 是否存在。
+创建 session 时 `storeId` 必填并验证存在;之后不再接受客户端传门店。消息和 fallback 请求必须携带当前 `draftVersion`,版本不一致返回 `DRAFT_VERSION_CONFLICT`。
+
M0 绝对不注册 `/confirm` endpoint。
### 4.6 模型输出强校验
@@ -214,6 +230,7 @@ M0 绝对不注册 `/confirm` endpoint。
"end": null
},
"remark": null,
+ "clearFields": [],
"ambiguities": [],
"nextAction": "resolve_context"
}
@@ -221,7 +238,7 @@ M0 绝对不注册 `/confirm` endpoint。
校验规则:
-- 未知字段拒绝或明确忽略,不得进业务实体;
+- 未知字段拒绝,不得进业务实体;
- `schemaVersion`、`intent` 和 `nextAction` 为枚举;
- 文本字段 trim、限长;
- 模型输出 ID、价格、号源或预约状态字段时整个响应降级;
@@ -299,7 +316,7 @@ frontend/src/pages.json # 路由
## 6. 业务事件与本体
-M0 候选事件:
+M0 已冻结事件:
- `booking_agent_started`;
- `booking_agent_draft_ready`;
@@ -307,15 +324,17 @@ M0 候选事件:
M0 不记录 `booking_agent_confirmed`,也不修改现有 `appointment_created` metadata,因为助手没有直接确认写入,而客户端的 `source` 字符串不能当作服务端归因事实。如后续需要精确联结「助手草稿 -> 表单创建」,另立 brief 设计可验证、一次性的草稿交接机制。
-编码前需增加或更新:
+Batch 0 已按 `documented` 证据增加:
-- `ontology/objects.md`:`booking_agent_session` gap/implemented 证据;
+- `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`;不得提前宣称已实现。
+
## 7. 自动化测试
### 7.1 后端必测
@@ -329,6 +348,7 @@ M0 不记录 `booking_agent_confirmed`,也不修改现有 `appointment_created
**会话和权限**
- 未登录、boss/staff 调用助手 endpoint 被拒绝;
+- 创建 session 缺少/伪造门店被拒绝,创建后不能跨店覆盖;
- A customer 无法查询、修改或结束 B customer session;
- session 过期后不再接收消息;
- M0 不存在 `/confirm` endpoint;
@@ -369,10 +389,12 @@ M0 不记录 `booking_agent_confirmed`,也不修改现有 `appointment_created
Owner:System Architect + Data Model + Product Design
+状态:**Completed — 2026-08-02**
+
- 冻结 `booking-intent-v1`、M0 状态子集、API shape 和业务码;
- 更新本体及机器图谱;
- 评审 session 表、TTL 和清理方式;
-- 完成 Access / Privacy Review。
+- 完成开发态 Access / Privacy Review;真实 provider 与生产复核仍在 Batch 4。
### Batch 1:供应商适配与契约测试
diff --git a/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md b/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md
new file mode 100644
index 0000000..20c73d8
--- /dev/null
+++ b/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md
@@ -0,0 +1,295 @@
+# 架构决策:智能预约助手 M0 契约与数据模型
+
+> 日期:2026-08-02
+> 状态:**Accepted for M0 implementation / 尚未实现**
+> Workstream:`Core Booking Flow`
+> Track:`Architecture`
+> Priority:`P0`(独立试验流,不高于 RC6 真实门店基线)
+> Owner Agent:`System Architect`
+> 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`
+> Access / Privacy Review Required:`Yes — development boundary accepted; live provider and production review pending`
+> RC Included:`No`
+
+**关联资产**
+
+- [产品与技术方案](./宠小它智能预约助手-产品与技术方案-v0.1.md)
+- [M0 编码任务 brief](./智能预约助手-M0编码任务brief-2026-08-02.md)
+- [M0 执行队列](./智能预约助手-M0执行队列-2026-08-02.md)
+- [`booking-intent-v1` JSON Schema](./contracts/booking-intent-v1.schema.json)
+- [M0 OpenAPI 契约](./contracts/booking-agent-m0.openapi.yaml)
+- [模型与语音供应商选型](./智能预约助手-模型与语音供应商选型-2026-08-02.md)
+
+## 1. 决策结论
+
+M0 冻结为 **「已登录 customer 在已选门店范围内,用文字或语音生成已验证预约草稿,再回填现有普通预约表单」**。
+
+冻结边界:
+
+1. 门店不是模型槽位。宠主必须先在 `CustAppointmentCreate` 选定门店,创建会话时只提交 `storeId`,服务端验证门店存在。
+2. 模型只输出通过 `booking-intent-v1` 校验的非权威文本约束,不输出或决定业务 ID、价格、服务事实、号源或预约状态。
+3. 宠物、服务、日期时间和号源全部由 Petstore 后端在 `current.userId + session.storeId` 范围内重新解析。
+4. M0 没有 `/confirm` endpoint,不调用 `AppointmentService.createBooking`,不修改 `Appointment` 表或状态机。
+5. 「带入普通预约」只返回一次页面级回填数据;最终仍由现有 `/api/appointment/create` 提交。
+6. 原始语音、当轮用户原文、模型请求/响应和完整对话不落库、不进业务事件、不进日志。
+7. 功能开关默认关闭,不进入 `phase2-pilot-rc6`。
+
+## 2. 为什么先选门店
+
+原方案允许助手在对话内收集门店,但会同时引入门店名称歧义、地理推荐、跨店服务匹配和无门店 `BusinessEvent` 等问题。当前产品仍是单店 SaaS 试点,不需要把门店发现变成 Agent 能力。
+
+M0 的次级入口仍位于 `CustAppointmentCreate`,行为调整为:
+
+1. 未登录:先登录并返回原预约页;
+2. 未选门店:先使用现有门店选择控件;
+3. 已选有效门店:创建智能会话;
+4. 会话内只解析该门店的服务与容量。
+
+这样可以让每个 session 和漏斗事件都有明确 `storeId`,并阻止模型扩大门店数据范围。
+
+## 3. `booking-intent-v1` 冻结
+
+模型输出以 [`contracts/booking-intent-v1.schema.json`](./contracts/booking-intent-v1.schema.json) 为唯一机器契约。
+
+### 3.1 字段语义
+
+| 字段 | 语义 | 是否业务权威 |
+|---|---|---|
+| `schemaVersion` | 固定 `booking-intent-v1` | 是,仅契约版本 |
+| `intent` | `book / modify / end / fallback` | 否,状态机重新判定 |
+| `petQuery` | 宠物展示名搜索词 | 否 |
+| `serviceQuery` | 服务名称/同义表达搜索词 | 否 |
+| `dateExpression` | 用户日期表达 | 否 |
+| `timeWindow` | `HH:mm` 起止偏好 | 否 |
+| `remark` | 用户希望带入预约的短备注 | 否,可编辑且限 200 字 |
+| `clearFields` | 用户明确要求清空的草稿字段 | 否,服务端只允许白名单字段 |
+| `ambiguities` | 模型观察到的歧义类型 | 否,服务端重新确认 |
+| `nextAction` | 下一步建议 | 否,编排器可忽略 |
+
+### 3.2 强校验顺序
+
+1. 响应体必须是单个 JSON object,不接受 Markdown fence 或前后说明文字;
+2. JSON Schema 校验通过,未知字段拒绝;
+3. 文本 trim、控制字符过滤和长度校验;
+4. 输出出现任意 ID、价格、手机号、号源或预约状态字段时整个响应降级;
+5. 服务端时间解析、归属解析和号源查询成功后才更新 `BookingDraft`;
+6. 失败不尝试从残缺 JSON 猜字段,返回 `AGENT_UNAVAILABLE` 并保留旧草稿。
+
+### 3.3 时间口径
+
+- 时区固定 `Asia/Shanghai`;
+- 支持绝对日期、今天/明天/后天、本周 X/下周 X、精确时间、上午/下午、X 点以前/以后;
+- 「过几天」「最近」「周末都行」等表达不得猜成绝对日期,必须返回日期选择项;
+- `appointmentTime` 只有在现有容量服务返回真实 slot 后才能写入草稿;
+- 对外时间字符串统一为 `yyyy-MM-dd'T'HH:mm:ss`,语义为 `Asia/Shanghai` 本地时间。
+
+## 4. M0 会话状态机
+
+```mermaid
+stateDiagram-v2
+ [*] --> collecting: create session
+ collecting --> collecting: 信息仍不足
+ collecting --> proposing: 可搜索真实号源
+ proposing --> collecting: 修改条件或需要澄清
+ proposing --> confirmable: 选择真实 slot
+ confirmable --> collecting: 用户修改草稿
+ collecting --> fallback: 用户回填表单或 LLM 降级
+ proposing --> fallback: 用户回填表单或 LLM 降级
+ confirmable --> fallback: 带入普通预约
+ collecting --> cancelled: 用户结束
+ proposing --> cancelled: 用户结束
+ confirmable --> cancelled: 用户结束
+ collecting --> expired: TTL 到期
+ proposing --> expired: TTL 到期
+ confirmable --> expired: TTL 到期
+```
+
+不允许的状态:`submitting`、`booked`、`needs_reselection`。终态 `fallback / expired / cancelled` 不接受新消息或转写。
+
+每次消息请求必须带客户端当前 `draftVersion`。服务端只在版本相等时更新,成功后加一;版本冲突返回 `DRAFT_VERSION_CONFLICT`,不得静默覆盖。
+
+## 5. API 冻结
+
+唯一契约是 [`contracts/booking-agent-m0.openapi.yaml`](./contracts/booking-agent-m0.openapi.yaml)。M0 只注册五个 endpoint:
+
+| Method | Endpoint | 结果 |
+|---|---|---|
+| `POST` | `/api/booking-agent/sessions` | 在已选 `storeId` 下创建 customer 会话 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/messages` | 提交当轮已确认文字并刷新草稿 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/transcriptions` | 转写短音频,文字返回输入框,不自动发送 |
+| `POST` | `/api/booking-agent/sessions/{sessionId}/fallback` | 返回已验证的部分/完整草稿并结束会话 |
+| `DELETE` | `/api/booking-agent/sessions/{sessionId}` | 结束本人会话 |
+
+### 5.1 身份与资源隐藏
+
+- 所有 endpoint 都必须经过 `AuthInterceptor`;
+- controller 再要求 `current.role == customer`,boss/staff 返回 `FORBIDDEN`;
+- session 每次以 `sessionId + current.userId` 查询;不存在和他人 session 统一返回 `SESSION_NOT_FOUND`;
+- `storeId` 只在创建时接收并验证,后续从 session 读取;
+- 音频转写 endpoint 必须挂在 session 下,客户端不得上传宠物/服务词表。
+
+### 5.2 响应语义
+
+沿用 `{ code, message?, bizCode?, data? }`。当前前端以 body `code` 判断业务结果;除 `AuthInterceptor` 的 HTTP 401 外,M0 不借机重构全局 HTTP status 行为。
+
+草稿中的 `storeId / petId / serviceTypeId / appointmentTime` 是经过后端重新解析的事实;客户端回填后,现有预约创建服务仍会重新执行最终权限、服务和容量校验。
+
+### 5.3 业务码
+
+| body code | bizCode | 语义 |
+|---|---|---|
+| 401 | `UNAUTHENTICATED` | session token 缺失或失效 |
+| 403 | `FORBIDDEN` | 非 customer 调用 |
+| 503 | `AGENT_DISABLED` | 功能开关关闭 |
+| 404 | `STORE_NOT_FOUND` | 创建时目标门店无效 |
+| 404 | `SESSION_NOT_FOUND` | session 不存在或不属于当前 customer |
+| 410 | `SESSION_EXPIRED` | 会话 TTL 到期 |
+| 409 | `SESSION_TERMINAL` | 会话已 fallback/cancelled |
+| 409 | `DRAFT_VERSION_CONFLICT` | 客户端草稿版本过旧 |
+| 400 | `INVALID_INPUT` | 空文本、超长或请求结构无效 |
+| 400 | `INVALID_AUDIO` | 空音频、格式/MIME/文件头不匹配或时长非法 |
+| 413 | `AUDIO_TOO_LARGE` | 超过 3 MB |
+| 429 | `RATE_LIMITED` | customer 请求频率超限 |
+| 503 | `AGENT_UNAVAILABLE` | LLM 超时、无效 JSON/schema 或外部失败 |
+| 503 | `ASR_UNAVAILABLE` | ASR 超时或外部失败 |
+
+宠物/服务歧义、无号源和未选时段是正常 `200` 会话响应,不用异常码表达。
+
+## 6. 数据模型冻结
+
+计划迁移:`backend/db/migrations/20260802_create_booking_agent_session.sql`。Batch 0 只冻结设计,不在 docs 任务内创建迁移。
+
+### 6.1 `t_booking_agent_session`
+
+| 字段 | 类型 | 规则 |
+|---|---|---|
+| `id` | BIGINT | 自增主键,只在后端和 `BusinessEvent.aggregate_id` 使用 |
+| `session_id` | VARCHAR(36) | UUID,唯一,对客户端暴露 |
+| `customer_user_id` | BIGINT | 必须是创建会话的 customer |
+| `store_id` | BIGINT | M0 必填;创建时验证存在,后续不接受覆盖 |
+| `status` | VARCHAR(24) | 仅六个 M0 状态 |
+| `draft_json` | TEXT | 最小已验证草稿,不存当轮原文/模型响应 |
+| `draft_version` | INT | 初始 0,每次成功草稿更新加一 |
+| `entry_source` | VARCHAR(32) | M0 固定 `appointment_create` |
+| `input_modality` | VARCHAR(16) | `text / voice / mixed`,首次成功消息后派生 |
+| `expires_at` | DATETIME | 创建时固定 `now + 30min`,M0 不滑动续期 |
+| `create_time / update_time` | DATETIME | 服务端时间 |
+
+索引:
+
+- `UNIQUE uk_booking_agent_session_public_id (session_id)`;
+- `INDEX idx_booking_agent_customer_status_expire (customer_user_id, status, expires_at)`;
+- `INDEX idx_booking_agent_status_expire (status, expires_at)`。
+
+不增加:
+
+- `appointment_id`;
+- `t_booking_agent_turn`;
+- 原始语音/转写/提示词/模型响应列;
+- `deleted`。会话是短期对象,使用明确终态并按 TTL 物理清理。
+
+### 6.2 TTL 与清理
+
+1. 会话固定 30 分钟有效,不因消息滑动延长;
+2. endpoint 发现过期时返回 `SESSION_EXPIRED`,并以幂等方式标记 `expired`;
+3. 每小时清理任务将到期活动会话标记 `expired`;
+4. `expires_at` 早于当前时间 24 小时的 session 物理删除;
+5. 清理失败记录数量和错误类别,不记录 `draft_json`;
+6. `BusinessEvent` 是独立低敏事实,不随短期 session 删除。
+
+## 7. 业务事件冻结
+
+M0 只新增三个首次漏斗事实:
+
+| 事件 | 触发 | 幂等键 | metadata 白名单 |
+|---|---|---|---|
+| `booking_agent_started` | session 创建成功 | `booking_agent_started:{sessionDbId}` | `entrySource` |
+| `booking_agent_draft_ready` | session 首次进入 `confirmable` | `booking_agent_draft_ready:{sessionDbId}` | `inputModality` |
+| `booking_agent_fallback` | 用户回填表单或 LLM 降级 | `booking_agent_fallback:{sessionDbId}` | `reason=user/llm_unavailable` |
+
+事件使用:
+
+- `storeId=session.storeId`;
+- `aggregateType=booking_agent_session`;
+- `aggregateId=session.id`;
+- `actorUserId=session.customerUserId`、`actorRole=customer`、`source=customer`;
+- 已有 `StoreCustomer` 时可关联,不能只为启动助手创建客户主档。
+
+禁止把输入原文、转写文本、备注、宠物名、服务名、供应商 request ID 或错误原文写入 metadata。
+
+M0 不写 `booking_agent_confirmed`,也不修改现有 `appointment_created` metadata。精确联结「助手草稿 -> 表单创建」需要另行设计服务端一次性交接机制。
+
+## 8. 外部调用与隐私门禁
+
+### 8.1 语音
+
+- 单段不超过 60 秒、3 MB;客户端预检,服务端按字节、文件头、MIME 和解析时长复核;
+- ASR 使用 `qwen3-asr-flash-2026-02-10` Base64 Data URL,不中转 OSS;
+- 上下文只包含当前门店服务名和当前 customer 的宠物展示名;
+- 返回转写后立即释放音频字节,不缓存、不写文件、不返回供应商 request ID;
+- 转写必须由用户编辑/确认后才能调用 messages。
+
+### 8.2 LLM
+
+- 使用固定 `qwen-plus-2025-12-01`、非思考、非流式、无工具、无联网;
+- LLM 超时 3 秒,ASR 超时 5 秒;不做多次自动重试;
+- 请求只包含当次文字、最小草稿和枚举说明,不包含手机号、token、客户时间线或其他门店数据;
+- API Key、Workspace ID 和 Authorization header 不进 Git、测试 fixture、聊天、截图或日志。
+
+### 8.3 频率门禁
+
+M0 默认每个 customer:
+
+- 10 分钟内最多创建 5 个 session;
+- 10 分钟内最多提交 30 条 message;
+- 10 分钟内最多转写 10 段音频。
+
+超限返回 `RATE_LIMITED`,普通预约继续可用。当前单实例可先使用进程内限流;进入多实例前必须切换共享限流状态。
+
+## 9. Review 结论
+
+### Architecture Review
+
+**通过,可进入实现。** 条件是严格保留五个 endpoint、无 `/confirm`、门店先选、普通表单独立可用、feature flag 默认关闭。
+
+### Data Model Review
+
+**通过提案,可由 Backend Core 实现迁移。** 相比原 brief 的修订:
+
+- 使用 `id BIGINT` 内部主键 + `session_id UUID` 外部唯一键;
+- `store_id` 从可空改为 M0 必填;
+- 去掉 `deleted` 和 `appointment_id`;
+- 增加固定 TTL、物理清理和 `draft_version` 并发规则。
+
+### Access / Privacy Review
+
+**开发边界通过,真实供应商 smoke 与生产开启未通过。** 不需要真实密钥即可实现接口、stub、状态机和测试;真实调用前仍需完成供应商数据处理确认、小程序隐私告知、费用告警和已知情语音样本评测。
+
+## 10. 后果与风险
+
+正向结果:
+
+- M0 不增加第二条预约写路径,模型失败不会污染 `Appointment`;
+- session 可短期恢复结构化草稿,又不形成对话原文数据库;
+- OpenAPI、JSON Schema 和本体可以直接驱动测试与 review;
+- 后续 M1 必须另立 ADR 才能新增 confirm/appointment 关联。
+
+已知限制:
+
+- 门店必须先选,无法用一句话同时完成「找店 + 预约」;
+- M0 不精确统计回填后最终创建转化;
+- 进程内限流只适用于当前单实例;
+- 供应商效果仍需真实语料验证,当前推荐不是生产准入结论。
+
+## 11. 实现门禁
+
+编码 PR 必须同时满足:
+
+- 引用本 ADR、OpenAPI、JSON Schema 和对应 `ontologyRefs`;
+- provider 单测只访问本地 HTTP stub;
+- 后端测试证明整个 M0 不调用 `AppointmentMapper.save`;
+- 未配置密钥或 feature flag 关闭时应用正常启动、普通预约正常;
+- 本体从 `documented` 更新为 `anchored` 只能与代码和测试同一批完成;
+- 真实供应商 smoke、生产配置和 RC 纳入必须重新授权。