T-0 / T-0b(门禁 · 2026-09-10) - T-0:sqlite 与 MySQL 结构对齐 —— core_holding 统一为 qty/cost_amount/as_of/pnl_pct + PK + UNIQUE(customer_id, product_id);补 core_product_nav;新增建库自校验 _assert_ddl_aligned()(R-g);test_db.py 增 3 条门禁用例(含反向验证门禁失效) - T-0b:DB 账号分离(D20)—— 新增 scripts/core/00-grant.sql(三账号逐表授权); settings.py 增 3 组账号;db.py 改 get_engine(db, role),缓存键改为 (库名, 角色), 账号未配置回退单账号;core_ro→ro / gateway_repository→rw / risk·session_repository→rw; tests/conftest.py 四处显式 role="admin"(R-e) T-1(数据层) - scripts/core/01-ddl.sql:新建 core_fee_rule / core_share_lot / core_convert_lot_detail; core_trade 加 convert_group_id + idx_convert_group;core_product 加 8 列 + fee_rate 补 COMMENT - 新增 07-seed-fee-rule.sql(赎回费 5 档 × 14 产品,按 22 号文 §10)/ 08-seed-share-lot.sql (58 行持仓 → 61 行批次,Σ remain_qty 恒等于 qty)/ 09-seed-org.sql(管理人 + TA + 申购费率 + 最低持有余额,v1.1 按「管理人全产品线」重排) - reset.ps1 追加 07/08/09;02-mysql-agent专用.sql 追加 risk_convert_detail - tests/_ddl.py 同步 4 表 + 新增 REQUIRED_CONVERT_TABLES 建库门禁 - 新增 scripts/dev/verify_convert_seed.py(pymysql 等价 reset 流程 + 8 条 DoD 断言, 含断言 ⑧「费率档 ↔ product_type 匹配」,越档即 FAIL) T-2 / T-2b(纯函数包 + 示例实算回填) - 新增 app/service/convert/ 7 文件:__init__ / types / calc / fee / nav / lot_bootstrap / errors (纯函数,不查库、不碰 SQL;所有量化显式 ROUND_HALF_UP;lot_bootstrap 用 zlib.crc32 保证 D18 跨进程同源) - 新增 tests/test_convert_calc.py 93 用例(12 类:HALF_UP 反向自证 / 分档边界 / FIFO 含同 confirmed_at 兜底 / 双口径 / 强制全转与强制赎回 / PRD §5.3 全链自证 / 纯函数零 IO 依赖断言) - 重写 scripts/dev/calc_convert_demo.py:去掉脚本内公式副本,改为调用生产 calc.py, 末尾与 PRD §5.3 逐项比对(不一致即退出码 1),兼作一致性门禁 验证 - pytest 609 passed / 3 skipped(516 → +93,零回归) - verify_convert_seed.py 8/8 PASS;calc_convert_demo.py 15/15 与 PRD §5.3 一致 文档:PRD v0.9.1(费率分类修正)· 架构 §7 签名回填 / §8.3 错误码注 / §15 T-2 完成 · 开发计划 §1.5 新增 R-h + §4.2·§4.3 执行记录 · AGENTS.md · docs/memory
94 lines
6.8 KiB
SQL
94 lines
6.8 KiB
SQL
-- ============================================================================
|
||
-- 00-grant.sql · DB 账号与最小权限(D20 · 架构设计-基金转换交易 §11.1)
|
||
-- ============================================================================
|
||
-- 目的:把「Core 只读」「审计不可删改」从**代码约定**升级为 **DB 级强制**。
|
||
-- 依据主架构评审 C1:原备选「代码层 SQL 白名单」已否决(注释/子查询/
|
||
-- 存储过程易绕过),独立最小权限账号是唯一可靠手段。
|
||
--
|
||
-- 执行:管理员(root)执行,且**不进 reset.ps1**
|
||
-- —— DROP DATABASE 不清除 mysql.db 中的授权行,本脚本只需执行一次;
|
||
-- 且建账号需管理员权限,与 reset.ps1 的运行账号不是一回事。
|
||
-- 命令:mysql -u root -p < scripts/core/00-grant.sql
|
||
--
|
||
-- ⚠️ 执行前:把三处 REPLACE_ME 换成 .env 中同名配置的真实密码。
|
||
-- 本文件**禁止提交真实密码**;改完执行,执行完不要提交本次改动。
|
||
-- 若 PyMySQL 报认证插件错误(MySQL 8.0+ caching_sha2_password),
|
||
-- 把 IDENTIFIED BY 改为 IDENTIFIED WITH mysql_native_password BY。
|
||
--
|
||
-- 与 settings.py 的对应(账号留空 → db.get_engine 回退 mysql_user):
|
||
-- xh_core_ro → mysql_core_ro_user / mysql_core_ro_password
|
||
-- xh_core_rw → mysql_core_rw_user / mysql_core_rw_password
|
||
-- xh_agent_rw → mysql_agent_user / mysql_agent_password
|
||
-- ============================================================================
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 1) xh_core_ro · jinrong_core 全库只读
|
||
-- 使用者:core_ro / core_tools / deps 归属校验 / risk 扫描,
|
||
-- 以及 **gateway 的校验读**(架构 §11.1 关键边界)
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE USER IF NOT EXISTS 'xh_core_ro'@'%' IDENTIFIED BY 'REPLACE_ME_core_ro';
|
||
GRANT SELECT ON jinrong_core.* TO 'xh_core_ro'@'%';
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 2) xh_core_rw · 仅 gateway 写路径:限 4 表,无 DELETE / 无 DDL / 无 GRANT
|
||
-- 使用者:gateway_repository.insert_trade · convert_core_repository.apply_convert
|
||
--
|
||
-- ⚠️ 关键边界:**gateway 读走 xh_core_ro、写走本账号**,两个 engine 并存。
|
||
-- 本账号**刻意不授** core_product / core_fee_rule / core_product_nav 的读权限,
|
||
-- 故 T-6 的 convert 计算链路读取产品/费率/净值**必须走只读账号**;
|
||
-- 不得为图省事给本账号补 SELECT ——否则 gateway 顺带获得全库读权限,
|
||
-- 「最小权限」落空(这是本任务最易走偏的一处)。
|
||
--
|
||
-- 表级 GRANT 可针对**尚不存在**的表:core_share_lot / core_convert_lot_detail
|
||
-- 由 T-1 创建,故本脚本可合法地先于 T-1 执行。
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE USER IF NOT EXISTS 'xh_core_rw'@'%' IDENTIFIED BY 'REPLACE_ME_core_rw';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_core.core_trade TO 'xh_core_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_core.core_share_lot TO 'xh_core_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_core.core_holding TO 'xh_core_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_core.core_convert_lot_detail TO 'xh_core_rw'@'%';
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 3) xh_agent_rw · jinrong_agent 业务表读写(无 DELETE)
|
||
-- 使用者:risk_repository / session_repository / convert_repository 等 Agent 侧全部
|
||
--
|
||
-- 不授 DELETE 的依据(T-0b 实测):app/ 与 scripts/ 全仓 **无任何
|
||
-- DELETE / TRUNCATE / DROP**,故不授 DELETE 不影响任何现有代码路径。
|
||
-- tests/conftest.py 的 teardown 需要 DELETE → 显式改用 role="admin"(R-e)。
|
||
-- ---------------------------------------------------------------------------
|
||
CREATE USER IF NOT EXISTS 'xh_agent_rw'@'%' IDENTIFIED BY 'REPLACE_ME_agent_rw';
|
||
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.agent_session TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.agent_message TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.agent_tool_call TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.input_guard_log TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_advisor_rel TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_profile_l1 TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_profile_l2 TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_profile_l3 TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.risk_alert TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.risk_suitability_log TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_threshold_config TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.customer_notify_log TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.advisor_draft TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.compliance_hit_log TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.analytics_query_log TO 'xh_agent_rw'@'%';
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.risk_aml_list TO 'xh_agent_rw'@'%';
|
||
-- risk_convert_detail 由 T-1 建表(本行可先于建表执行)
|
||
GRANT SELECT, INSERT, UPDATE ON jinrong_agent.risk_convert_detail TO 'xh_agent_rw'@'%';
|
||
|
||
-- 审计表:**追加写、不可改不可删**
|
||
-- 口径澄清(T-0b 修正架构表述):架构 §11.1 原文写「audit_log 只授 INSERT」,
|
||
-- 字面执行会连 SELECT 一并剥夺,导致 risk_repository.has_engine_error_audit
|
||
-- (:231 SELECT 1 FROM audit_log)与 list_audit_events(:420 SELECT *)双双失败。
|
||
-- 红线本意是「**只能追加,不能修改或删除**」→ 正确授权 = SELECT + INSERT。
|
||
GRANT SELECT, INSERT ON jinrong_agent.audit_log TO 'xh_agent_rw'@'%';
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- 4) 执行后自检(管理员执行,非必需)
|
||
-- ---------------------------------------------------------------------------
|
||
-- SHOW GRANTS FOR 'xh_core_ro'@'%'; -- 期望:仅 SELECT ON jinrong_core.*
|
||
-- SHOW GRANTS FOR 'xh_core_rw'@'%'; -- 期望:4 表 SELECT/INSERT/UPDATE,无 DELETE、无 ALL
|
||
-- SHOW GRANTS FOR 'xh_agent_rw'@'%'; -- 期望:业务表三权限;audit_log 仅 SELECT, INSERT
|
||
-- 注:CREATE USER / GRANT 会自动刷新权限缓存,无需 FLUSH PRIVILEGES。
|