Files
group_fqcd_jr/docs/evidence/20260909-environment-facts.md
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

94 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运行环境事实与前置条件(2026-09-09 实测)
本文件汇总**在本机实测确认**的运行约束,供接入方与运维在部署前核对。每条都注明是实测还是推断;
没有实测支撑的内容不写。
## 一、结论速览
| 事实 | 影响 | 处置 |
|---|---|---|
| `model_endpoint_config` 表 0 行 | `IntentClassifier` 在端点为空时按设计失败关闭(抛 `RecoverableAgentError`),run 永远进不了 `complete_run` | **接入前必须配置至少一个模型端点与一个受控备用端点** |
| 无 embedding 端点 | 记忆语义(向量)召回降级为 `embedding_failed`,结构化召回仍可用 | 需要语义召回时配置 embedding 端点;代码已就绪,无需改动 |
| Worker 未常驻运行 | Outbox 事件持续堆积(实测 262 条 pending) | 部署常驻 Worker:`python -m app.worker` |
| `dispatch_one` 每轮只领取一条事件 | 清空 262 条积压约需 262 轮(默认轮询 1 秒) | 积压深度纳入运维观测;见「五」 |
| 记忆召回有 Redis 热缓存(TTL 300 秒) | 缓存命中会短路数据库查询 | 写入路径需失效缓存;见「四」 |
## 二、数据库
- 业务表 **51 张**(另加 `alembic_version`),`head = 20260909_memory_active_key`。
- 本机库名 `jr`,连接与凭据来自 `.env` 的 `MYSQL_DSN`(`tools/` 下脚本已不再硬编码口令)。
- 三件审计工具(提交前建议全跑):
```powershell
python tools/audit_schema.py # 表与列存在性
python tools/audit_constraints.py # 文档唯一键 ↔ 库唯一索引 ↔ ORM 列名
python tools/schema_fingerprint.py # 字段+索引+外键结构指纹
```
- **由脚本或人工建起的库(有表但没有 `alembic_version` 记录)不能直接 `upgrade`**,会重建已有表而失败。
先跑 `python tools/migration_state_check.py`,按提示 `alembic stamp <版本>` 再 `upgrade head`。
## 三、模型与向量化
- 文本生成入口:`ModelGenerationService`(经 `ModelDispatchService` 受控降级),端点来自
`model_endpoint_config`(`status=active`),密钥必须是 `env:` 引用。
- 文本向量化入口:`ModelEmbeddingService`,调用 OpenAI-compatible `/embeddings`(与文本生成同族)。
- **端点缺失一律失败关闭**:意图分类、记忆抽取、向量化都不会"猜"或返回默认值——这是设计意图,
不是缺陷。它会表现为 run 失败(`AGENT_INTERNAL_ERROR`)或召回带 `embedding_failed` 降级标记。
- 记忆抽取失败时**不写入任何记忆**(宁可不记,也不落用户原文)。
## 四、中间件(实测状态)
- **Milvus**:可达。`VectorMemoryAdapter` 构造成功(实测 `type(adapter).__name__ == "VectorMemoryAdapter"`),
语义通道已接入召回链路。
- **Redis**:可达。召回缓存的读写与失效均生效(实测缓存命中 `from_cache=True`、客户级失效返回删除键数)。
Redis 不可用时缓存读写失败不阻塞召回。
- **Neo4j**:可达(连通性检查通过);关系投影由 `GraphProjectionWorker` 承担。
- 记忆召回的缓存键前缀为 `mem:recall`(`MemoryRecallService.CACHE_KEY_PREFIX`)。**任何失效逻辑都必须
复用 `MemoryRecallService.cache_keys()`**,历史上出现过清理 `mem:mid:hot:` 这种不存在的键、导致失效空转的问题。
## 五、Worker 与事件队列
- Worker 是**独立进程**:`python -m app.worker`(`--once` 只消费一次后退出)。
- `WorkerRuntime` 注册的 handler:`agent.run_requested`、`memory.extraction_requested`、
`agent.run_completed`、`config.cache_invalidate_requested`、`memory.deletion_requested`。
**未注册 handler 的事件类型会永久滞留 pending** —— 新增事件类型时必须同时注册消费者。
- 实测 `jr` 库:`agent.run_requested` pending 262 / failed 1 / dead 1;无任何记忆事件(因为当时没有
run 能成功进入 `complete_run`)。
- episode 聚合是低频批处理(每 30 轮触发一次),幂等键为 `episodes.content_hash`。
- **常驻能力**:`serve()` 内层已加单轮异常兜底(记录堆栈 → 退避 → 继续),因此数据库抖动、
迁移锁表、外部依赖瞬断不会让常驻进程整体退出;`--once` 模式保持抛出,便于诊断。
- **⚠️ 跑验收前必须先停 Worker**:`tools/acceptance_check.py` 与常驻 Worker **共享同一条
`agent_run` 队列**。Worker 会抢先领取验收创建的 run,用**生产工厂**(未注册探针 Agent)执行,
于是 run 以 `AgentTypeNotFoundError` 失败、验收出现 4 项 FAIL——这是共享队列的固有竞争,
不是缺陷。停掉 Worker 后同一命令即 7 PASS。
- **本会话启动的 Worker 是后台任务,不保证跨会话存活**。要真正常驻请在自己终端拉起
`D:\conda\envs\jr_py313\python.exe -m app.worker`,或注册为系统服务/计划任务。
- **同一开发库不要并发跑 Worker 与验收**:多进程同时消费会互相抢 run(实测出现过一次)。
## 六、认证与权限
- 鉴权链:`Authorization: Bearer <JWT>` → `JwtAuthenticator`(RS256,校验 iss/aud/nbf/exp/jti)→
`IdentityService.resolve` 实时重载角色/权限/客户归属。
- JWT 公钥在**进程内首次请求时加载一次**(`lru_cache` 单例):**轮换密钥需重启进程或清缓存**。
- `tools/seed_test_rbac.py` 用于造测试账号(实测存在客户 `9001`、风控 `9002`)。
注意权限码必须与实际工具声明一致:`check_suitability` 需要 `suitability:read`、
`query_fund_quote` 需要 `fund:quote:read`、运行受理需要 `agent:run`。
## 七、本地开发环境
- Python 解释器必须用项目专用环境:`D:\conda\envs\jr_py313\python.exe`
(系统默认 `python` 是 conda base,缺少 `asyncmy` 等依赖,直接跑 pytest 会在 `conftest` 导入处失败)。
- 质量门禁命令:
```powershell
D:\conda\envs\jr_py313\python.exe -m pytest -q tests/unit tests/contract -p no:cacheprovider
D:\conda\envs\jr_py313\python.exe -m pytest -q tests/integration -p no:cacheprovider # 需真实 MySQL
D:\conda\envs\jr_py313\python.exe -m ruff check app tests tools alembic
D:\conda\envs\jr_py313\python.exe -m mypy app # strict
```
- 集成测试真连本地 MySQL `jr`,写数据必须自行清理;`tests/integration` 下所有文件均已打
`integration` marker(此前 5 个文件漏打,按 marker 过滤会静默漏测)。
- 端到端链路验收用 `python tools/memory_chain_probe.py`(自带清理,可重复运行)。