lzf_0626
468ae0bd47
fix(admin): 激活意图不再归档同 Agent 的其他意图(风控 4 意图曾只剩 1 条生效)
...
两个相关的缺陷,都属于**静默失效**型。
1. `admin_service._transition_intent_config`(真正的 bug)
激活一个意图时按 `agent_type` 过滤旧 active 版本并归档,但唯一键是生成列
`active_key = concat(agent_type, ':', intent_code)` —— 同一 `agent_type` 下
**不同意图码本就允许并存**。于是激活 `general` 会把
`risk_overview` / `risk_search` / `risk_evidence` 一并归档:风控运行期只剩 1 条
active 意图,问"查看当前风险概览"被分到 `general`(confidence 0.5),
而**没有任何报错**。该函数自己的 docstring 写的正是正确行为,实现与它不符。
2. `tools/publish_risk_agent_config.py`(使脚本无法自愈)
按 `intent_code` 单键建 dict 收集现有意图,而列表**按 id 倒序**返回且同一意图码
有多个版本,于是**旧版本覆盖新版本**;取到 v1 后再"版本 +1"算出的正是已被占用的
v2,创建必然 409 IDEMPOTENCY_CONFLICT。现象是 4 条意图全部"创建失败"而库里
其实都有,`tools/seed_demo_data.py` 第 7 步因此必然失败。
修复后实测:
- 库里 4 个风控意图全部 active;
- 问"查看当前风险概览" → `intent=risk_overview`、`confidence=1.0000`
(修复前为 `general` / 0.5);
- `tools/seed_demo_data.py` 10/10 步完成、退出码 0(修复前第 7 步退出码 1)。
回归测试:`test_activating_one_intent_does_not_archive_sibling_intents`。
已确认把修复回退后该用例**确实失败**(`['beta'] != ['alpha', 'beta']`),
不是永远通过的空测试 —— 原有用例盖不住这个缺陷,因为它建的第二个意图始终停在 draft、
从未激活过。
门禁:ruff 通过;mypy 250 文件 0 错;unit+contract 1381 passed / 0 failed;
integration 104 passed;e2e 冒烟 40/40。
2026-09-13 22:08:04 +08:00
yuancong_0626
a501060e50
merge: merge yc into qyqy_develop
2026-09-12 12:50:14 +08:00
yuancong_0626
91f5373cfb
袁聪的第三次提交,API接口完善
2026-09-12 12:20:01 +08:00
lzf_0626
441364437c
merge: 合并 ZSY 的客服 Agent 接入(访客身份、画像候选、转人工工单)—— PR #7
2026-09-12 12:05:25 +08:00
张胜宇
e85989b344
merge: integrate latest qyqy_develop (auth login + RBAC read) into ZSY branch
...
Incremental merge on top of ef701c8 , which already integrated the earlier qyqy base bbf623a . qyqy_develop only added commits on top of bbf623a , so this merge is conflict-free. Incoming: account/password login (POST /api/v1/auth/tokens), RBAC read-only query API, rate limit dependency, login test console and user management tools. Additive changes in app/main.py, requirements.txt and pyproject.toml from both sides are all preserved. ZSY side capabilities (visitor tokens, customer service agent, knowledge retrieval, profile projection) are unchanged.
2026-09-12 10:37:53 +08:00
张胜宇
ef701c844c
merge: integrate ZSY customer service and profile capabilities
2026-09-11 22:31:51 +08:00
lzf_0626
5018f11843
增加投顾(advisor)角色;修正登录测试的错误假设;修投顾带入的 2 处文档重号
...
## 投顾角色
投顾线合并后,bootstrap.py 有 10 处 allowed_roles 引用 advisor,
financial_nl2sql_service.py:272 还硬编码检查 {"advisor","operator","admin","super_admin"},
promotion_material_service.py:164 按 "advisor" in context.roles 走业务分支 ——
但 sys_role 里没有这个角色、sys_permission 里也没有投顾那 16 个权限码(种子只建到 9019)。
表现是所有投顾接口 403,而报错看起来像"权限配错了",实际是角色根本不存在。
- tools/grant_advisor_role.py:建 advisor 角色(id=9004,避开种子的 9001-9003 重建范围)
+ 16 个投顾权限(id 9020-9035)+ 授权(advisor 拿 10 项工作流、admin 补齐 16 项)。
只增不删、可重复执行、带 --dry-run。
- tools/create_test_user.py:ROLE_IDS 加 advisor。
- 先跑 alembic upgrade head:补 21 张 advisor_* 表,业务表 68 → 89,审计通过。
权限划分:投顾工作流 10 项(read:self / generate:self / review / publish)给 advisor;
治理类 6 项(product-governance:*、profile-governance:*、asset-allocation:backtest)只给 admin。
review/publish 也给 advisor,与既有决策一致(此前已裁定不做双人复核)。
验证:advisor_t 登录 200,roles=['advisor'] data_scope=all 权限 10 项;
用它查 RBAC 清单得 403(没有 audit:read),边界正确。
⚠️ 与种子的冲突:seed_test_rbac.py 是 DELETE 重建语义,其
DELETE FROM sys_permission WHERE id BETWEEN 9001 AND 9099 会清掉本脚本建的权限。
要把投顾权限固化,应并进 seed_test_rbac.py 的 PERMISSIONS 常量。
## 修正登录测试的一个错误假设
test_issued_token_actually_works_on_a_real_endpoint 原本用客户的
/users/me/memory-profile 验证令牌可用,投顾合并后它返回 403。追下去发现**与令牌无关**:
那个接口对客户有业务前置"请先完成开户风险测评问卷",而演示客户 9001 没有测评记录。
是我的测试选错了验证端点,把"业务前置未满足"误判成"令牌坏了"。
- 改用管理员令牌调 /api/v1/admin/roles(需要 audit:read,走完整鉴权链路),
并补一条反向对照:不带令牌必须 401,否则那个 200 说明不了令牌有效。
- 把那个业务前置单独写成一个用例,让后来者一眼看到条件,而不是反复怀疑令牌。
过程里我先按控制台乱码猜了两次失败原因,都不对;最后把响应抓成 UTF-8 文件才看到真实
消息。教训记下:不要读乱码猜消息。
## 修投顾带入的 2 处文档重号
21-投顾Agent迁移TODO.md → 30-…、22-投顾Agent灰度与回滚操作手册.md → 31-…
(沿用 NL 那次让号的先例:既有文档更早、引用更多;且这两份新文档没有被任何地方引用。)
文档守卫:40 份无编号冲突。
验证:ruff 干净 / mypy 228 文件 0 错 / 文档守卫 40 份无冲突 /
unit+contract 1207 passed(0 failed)/ integration 99 passed / 业务表 89 张。
2026-09-11 21:49:46 +08:00
lzf_0626
6812fbe317
B1:RBAC 只读查询接口(4 个)+ docs/05 登记 A035-A038
...
在此之前,权限只能靠脚本改,平台里**没有任何地方能看"谁能访问什么"**。
B1 先把"看得见"做出来:
- GET /api/v1/admin/roles 角色清单 + 权限数 + 在用人数
- GET /api/v1/admin/roles/{role_code} 角色详情
- GET /api/v1/admin/roles/{role_code}/permissions 权限清单(按权限码排序,
便于与各 Service 的 require() 对照)
- GET /api/v1/admin/users/{user_id}/roles 某人**实际解析出来**的角色/权限/数据范围
几个刻意的决定:
1. 权限码复用 `audit:read` 而不新增 `rbac:read`:这份清单本身就是审计材料,且复用是零数据
改动、立刻可用(新增权限码要先改 sys_permission,而它目前由 seed_test_rbac.py 以
DELETE 重建语义管理)。将来要细分再加,不冲突。
2. `/users/{id}/roles` 直接复用 IdentityService.resolve,不自己拼 SQL —— 那正是请求进来时
走的链路(status 检查、assigned_at/expires_at 时间窗、data_scope 取最高、客户分配)。
自己写一遍必然漂移,而"这里查出的权限"与"实际能用的权限"不一致比没有这个接口更糟。
测试里加了一条交叉验证:该接口的 roles/data_scope 必须与登录响应完全一致。
3. 停用账号返回**空权限集**而不是 404 —— 用户存在但拿不到权限,如实呈现比 404 更利于排障。
4. 角色详情与权限清单分开:权限为空的角色不该被误判成"角色不存在"。
5. 全部只读、不写审计(它们返回的就是审计材料本身),docs/05 §19 登记时审计列标"否"。
权限变更(提权/降权)仍无接口 —— docs/05 §19 已注明那不是遗漏,而是需要单独评审
(审计留痕 + 禁止自我提权 + 保护内置角色三条红线)。
另:确认了 load_context 对 sys_user_role.expires_at 是有过滤的(assigned_at<=now AND
(expires_at IS NULL OR expires_at>now)),我上一轮只读了半段 SQL 差点误报。
验证:ruff 干净 / mypy 185 文件 0 错 / 文档守卫 38 份无编号冲突 /
unit+contract 1140 passed / integration 98 passed(本批新增 8 个)。
2026-09-11 21:22:56 +08:00
lzf_0626
01ee68034d
新增登录接口:账号密码换访问令牌(POST /api/v1/auth/tokens)
...
背景:客户 / 员工 / 管理员三种身份此前无法区分。但区分逻辑其实早就完备 ——
bootstrap.py 里各 Agent 的 allowed_roles 一直是分开的(CustomerServiceAgent 只要
customer、RiskAgent 要 risk_operator/admin、PlatformProbeAgent 只要 admin),
唯独缺"怎么证明你是谁";sys_user.password_hash 字段也一直存在,只是全是占位符
(种子写 'x'、worker 身份写 !worker-only-no-password-login!),从没写过真实密码。
实现:
- app/service/auth_service.py:bcrypt 校验 + 签发只含 sub 的 JWT + 审计。令牌里只放 sub
是刻意的:角色/权限/数据范围由 IdentityService 每次请求查库解析
(identity_repository.load_context),权限变更因此立即生效,现有鉴权链路一行未改。
- app/api/controllers/auth.py + app/api/schemas/auth.py:POST /api/v1/auth/tokens,
响应含 roles/data_scope 供前端决定进哪个界面(鉴权仍以库里实时数据为准)。
- tools/set_user_password.py:设密码(客户 123456 / 员工 666666 / 管理员 88888888)。
⚠️ 脚本与文档均标注"仅限演示环境",这三种弱口令上线前必须更换。
- pyproject / requirements 加 bcrypt(cryptography 只用于 JWT,不提供密码哈希)。
安全约定(逐条有实现与测试):失败不区分原因 —— 用户不存在/密码错/账号停用返回同一条
401,否则接口就成了账号枚举器;用户不存在时也跑一次 bcrypt 以抹掉时序差异;
成功与失败都写 interaction_audit(actor_id 可空正是为失败场景准备的);绝不记录密码。
过程中踩到一个自己挖的坑:给登录路由挂了通用的 enforce_rate_limit,而它声明依赖
build_request_context ⇒ 变成"要登录先登录",所有登录都 401。改为新增
enforce_login_rate_limit:按客户端 IP 独立限流(60 秒 10 次)、不依赖认证上下文。
集成测试据此调整:注入恒放行替身隔离跨用例的计数累积,同时保留一个恒超限用例验证闸门
确实会拦 —— 不能因为加了替身就把这道防线测丢。
接口登记:docs/05 §19 加 A034;并更新 §11 —— 那里原写"JWT 签发、刷新、注销由统一身份
认证模块负责,Agent 平台不重复实现",现注明平台只做登录这一步,刷新/注销仍归该模块。
验证:ruff 干净 / mypy 183 文件 0 错 / 文档守卫 37 份无重号(此前因 docs/21 重号失败)/
unit+contract 1140 passed / integration 90 passed。
2026-09-11 20:43:21 +08:00
qyqy
7677aeaee1
merge: 并入同事的场外申购/推广/行情/NL2SQL 线(11 提交、334 文件)
...
冲突仅 3 个文件,全部取并集(双方都没有需要丢弃的改动):
- app/main.py:import 双方路由(我方 knowledge_management + 同事的 offsite_fund/
promotion_material);include_router 段本已自动合并
- app/service/agent/bootstrap.py:import 与工具注册均取并集
(query_customer_profile + query_financial_data 都注册)
- tests/integration/test_config_release_mysql.py:outbox 清理同时保留
架构师的 event_type 限定(防误删其它域 outbox 行)与同事新增的 peer_release_id
同事这轮带入:11 个 alembic 迁移(建 offsite_* / promotion_* 等表)、
场外申购与推广素材 Agent、financial NL2SQL 工具。
注意:本库尚无 offsite_*/promotion_* 表,跑相关测试前需要执行 alembic upgrade。
边界核对:同事的场外代码未写入场内交易表(fin_sim_order/fin_capital_flow/fin_cash_ledger),
符合 AGENTS.md 规则 8。
2026-09-11 18:56:26 +08:00
张胜宇
1eb04f28b1
test: fix mysql profile snapshot cleanup
2026-09-11 18:09:36 +08:00
yuancong_0626
3f7c5ca74f
merge: 同步 origin/qyqy_develop(风控扫描、知识检索、客服 Agent、协商等)
...
冲突处理:均为双方各自新增,按并集保留——
- .gitignore:本地 data/logs 忽略项 + 远端 .dsh-drop/
- app/core/config.py:offsite/promotion 与 risk_scan 配置项并存
- app/service/agent/bootstrap.py:场外/推介/风控/NL2SQL/知识检索 工具与 Agent 全部注册
- app/worker/__main__.py:场外邮件 Worker 接线 + runtime 关系服务/投影清理注入并存
收尾:新增 20260911_merge_risk_heads 收敛迁移双 head;按 docs/21 生成 config/jwt/dev 开发密钥。
2026-09-11 17:32:47 +08:00
yuancong_0626
61861cdef6
袁聪merge:合并分支
2026-09-11 17:26:18 +08:00
yuancong_0626
fd9598efdf
袁聪的第二次提交,项目已完整
2026-09-11 16:57:47 +08:00
张胜宇
ef098e6a4b
feat: complete customer service safety and handover flow
2026-09-11 16:11:30 +08:00
qyqy
cbd6de2721
Merge remote-tracking branch 'origin/qyqy_develop' into nl-merge-latest
...
# Conflicts:
# app/service/model_gateway.py
2026-09-11 15:38:11 +08:00
wangjianlong_0626
e4c4099aaa
wip: 客服Agent + RAG + 画像收尾(基于 6516ccb)
2026-09-11 14:46:40 +08:00
lzf_0626
15866d4564
风控写接口接入平台幂等(docs/25 P3 #22)
...
docs/05 §5.1 把"业务写接口"列为必须携带 Idempotency-Key 的接口,风控 6 个 POST
(手工扫描、确认接收、进入调查、关闭误报、完成结案、升级处理)此前一个都没带,
重复提交会二次驱动状态机。
复用平台的 api_request_receipt 与 ApiTransactionService,但新增 execute_in:原来的
execute 自己开 SessionFactory() 和 session.begin(),而 RiskActionService._finish
会在内部 commit,套进去就成了"内层提交外层事务"。execute_in 改为在调用方传入的
session 上读写幂等记录,幂等记录因此与业务写入同处一个事务(§5.2)。
scope 用实际路径(含 alert_no)而不是路由模板:§5.1 的幂等范围是
user_id + method + normalized_path + idempotency_key,把路径参数折成模板会让同一个键
在不同预警之间互相回放 —— 那是把两次不同资源的操作当成一次。
测试:单测加幂等透传替身与"缺键即 422"用例;新增
tests/integration/test_risk_idempotency_mysql.py,覆盖同键回放不重复执行、同键不同
正文 409、缺键/非 ASCII 拒绝,以及端到端"重复 POST 只调用一次处置逻辑"。
2026-09-11 14:12:56 +08:00
张胜宇
8e36a9941d
merge: preserve existing business features on new foundation
2026-09-10 17:26:52 +08:00
lzf_0626
6516ccb385
feat: 第二版——接口契约对齐 docs/05,修复静默故障与数据库基线
...
相对第一版 46fc976 的完整变更。组员迁移对照表见 docs/20。
一、对外契约对齐 docs/05(破坏性,共 4 处,组员需按 docs/20 调整)
1) 配置发布端点改为文档规定的复数资源名:submit→validations、
approve→reviews(需 body decision)、activate→activations、
rollback→rollbacks;第一版这 4 个动词式路径 docs/05 从未定义过。
2) 错误码由 8 个笼统码改为 15 个具体语义码(FORBIDDEN→AGENT_PERMISSION_DENIED、
UNAUTHORIZED→AUTHENTICATION_REQUIRED、CONFLICT→RESOURCE_VERSION_CONFLICT、
RESOURCE_NOT_FOUND→RUN_NOT_FOUND/SESSION_NOT_FOUND 等),
输入类错误状态码 400→422。
3) POST /api/v1/agent-runs 与 GET /api/v1/agent-runs/{run_id} 统一为
{data, meta} 信封(data 内字段名与语义未变)。
4) 错误响应体统一为 {error:{code,message,retryable,field_errors}, meta:{trace_id}},
不再返回 FastAPI 默认的 {"detail": ...}。
二、数据库基线与约束
新增 39 张表的基线迁移(链根)与联合唯一键纠偏(4 张表、删 8 增 4,幂等收敛);
撤下 config_release 的双人复核 CHECK(应用层已允许自审,审核节点保留,
自审如实写入 reviewer_id);记忆 active key 生成列与唯一键;
activate 开始记录 supersedes_release_id 使版本链可追溯。
docs/00 基线未修改,未重命名或删除任何表与字段。
三、修复会静默出错或无报错的缺陷
- 跑完集成测试后平台会静默失去生效配置:清理只删自己创建的版本,却没有恢复被它
顶成 superseded 的原生效版本,且审计一并删除因而完全无痕,表现为所有工具被拒
但没有任何报错。已修清理逻辑并加恢复。
- Worker 单轮异常导致进程退出;记忆抽取调用方的“事务已开始”异常;
召回缓存丢失 degraded 标记;连接时区未生效导致 created_at/updated_at 差 8 小时;
.env 与 os.getenv 密钥来源分裂导致“没有可用的已批准模型端点”。
- 记忆信号识别漏判与跨键误命中;SSE 未带 Accept 的协商行为。
四、功能补齐
记忆链路 P1/P2/P3(抽取、受控词表、召回与缓存、生命周期级联及投影事件)、
fin_* 场内交易只读 ORM 层、agent_intent_config 状态流转并在运行期真正生效、
限流(Redis 固定窗口、故障一律放行)、游标校验、trace_id 中间件、
示例业务 Agent fund_query_demo 与一键端到端验证脚本,以及审计/指纹/迁移状态工具。
五、文档与验证
新增 docs/19(业务 Agent 接入实操)、docs/20(第一版迁移指南)与 docs/evidence 证据;
docs/01/02/06/08/09/17 同步实现现状。
验证结果:ruff 通过、mypy 103 文件无错、unit+contract 447 passed、
integration 29 passed、acceptance_check --production 7 PASS、
demo_agent_e2e 9/9 PASS(含失败关闭反证)。
2026-09-10 15:55:54 +08:00
yuancong_0626
5907fcd6d2
袁聪的第一次提交,包含nl2sql,行情数据,场外申购
2026-09-10 09:23:22 +08:00
Codex
b1497fd2c6
chore: initialize project repository
2026-09-09 21:55:37 +08:00