fix(compliance): A-2 admin/外部渠道补录同意审计统一化
LEGAL_PRIVACY_BASELINE §4/§6 要求任何订单创建路径必须记录同意审计字段。
portal 路径已落 consent_channel=portal / consent_operator=guardian (见
web_public.py + intake_store.save), admin 路径 6/20 之前完全不入任何 consent
字段 (admin/routes/orders.py grep 'consent' 0 命中), 形成合规盲区。
落地:
- CreateOrderRequest 新增必填 consent: ConsentInfo
- consent_method Literal: verbal_chat / phone_recording / screenshot /
written_form / self_declared
- consent_note Optional[str]
- 缺失或非法 → HTTP 422
- Order 模型 + DAO _WRITABLE_COLUMNS 加 consent_method / consent_given_at
(冗余落库避免每次列表 join order_intakes)
- schema 增量: ALTER TABLE orders ADD COLUMN consent_method / consent_given_at
(幂等)
- create_order 同步写 order_intakes (独立 IntakeStore.for_db, 不复用 OrdersDAO
conn — T12-D conn ownership 修复保驾)
- consent_channel = payload.source (xianyu/wechat/school/web)
- consent_operator 严格按基线白名单 self/guardian/admin_import:
- web 渠道: 'guardian' (与 intake_store.save 默认值一致)
- 其他渠道: 'admin_import' (后台代录, 同意来源是渠道商)
- consent_method / consent_given_at / consent_note 落库
测试:
- test_create_order_rejects_missing_consent_block[xianyu|wechat|school|web] (4 个)
- test_create_order_writes_intake_record_with_consent_audit
- test_create_order_external_channel_marks_consent_operator_as_admin
- test_create_order_rejects_invalid_consent_method
- test_order_detail_returns_consent_method_and_given_at
- 更新 test_create_order_returns_masked_payload_with_history (加 consent)
- 更新 test_admin_orders_alias_list_and_detail (加 consent)
验证:
- 25/25 admin/tests/{test_routes_orders,test_admin_alias_routes} 通过
- ruff + mypy 通过
- 端到端 smoke: 4 笔订单 (2 terminal + 1 pending + 1 paid-in-window) 实测
包含 consent 字段全部通过
- dev-verify: 1186 passed / 2 failed (失败的 2 个均为 worktree 环境限制:
test_backup_restore_service_level 的 subprocess 路径假设 + 6/19 已知问题,
与本改动无关; main 上单跑同样测试通过)
This commit is contained in:
@@ -141,6 +141,10 @@ _WRITABLE_COLUMNS: tuple[str, ...] = (
|
||||
"notes",
|
||||
"tags",
|
||||
"upgrade_from",
|
||||
# A-2 (2026-06-20) — 后台/外部渠道补录同意审计统一化
|
||||
# 冗余字段, 避免每次列表 join order_intakes。
|
||||
"consent_method",
|
||||
"consent_given_at",
|
||||
)
|
||||
|
||||
# 历史阶段字段映射:状态进入时自动置位的 timestamp 字段。
|
||||
|
||||
@@ -91,6 +91,12 @@ class Order:
|
||||
tags: List[str] = field(default_factory=list)
|
||||
upgrade_from: Optional[str] = None
|
||||
|
||||
# 同意审计(A-2 2026-06-20 落地,冗余落库避免每次列表 join order_intakes)
|
||||
# - consent_method: 见 routes/orders.py 中 ConsentInfo 的 consent_method Literal
|
||||
# - consent_given_at: ISO8601, 与 order_intakes.payload_json.consent_given_at 同源
|
||||
consent_method: Optional[str] = None
|
||||
consent_given_at: Optional[str] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""自动派生 hash 与时间戳。"""
|
||||
if self.customer_phone and not self.customer_phone_hash:
|
||||
|
||||
@@ -106,6 +106,14 @@ def apply_schema(db_path: str | Path) -> sqlite3.Connection:
|
||||
}
|
||||
if "customer_email" not in columns:
|
||||
conn.execute("ALTER TABLE orders ADD COLUMN customer_email TEXT")
|
||||
# A-2 (2026-06-20) — 后台/外部渠道补录同意审计统一化
|
||||
# consent_method 记录采集方式(verbal_chat/phone_recording/screenshot/
|
||||
# written_form/self_declared), consent_given_at 记录采集时间。
|
||||
# 两个字段都冗余落库, 避免每次列表 join order_intakes。
|
||||
if "consent_method" not in columns:
|
||||
conn.execute("ALTER TABLE orders ADD COLUMN consent_method TEXT")
|
||||
if "consent_given_at" not in columns:
|
||||
conn.execute("ALTER TABLE orders ADD COLUMN consent_given_at TEXT")
|
||||
conn.commit()
|
||||
except Exception:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user