pipeline-service/README.md
ymq 25c2452647 feat(secret_vault): 用户敏感信息管理模块(2026-09-17)
敏感信息入库 + 会话占位符引用 + 入站自动侦测 + 执行边界注入,真值全程不进
模型上下文/不落库/不进命令字符串(根治"用户贴apikey→每轮上行供应商"泄露面)。

核心 secret_vault.py:
- AES 密文入库(appPublic.aes + UTF-8包装 + AES1$版本前缀)。注:ahserver 的
  password_encode 实测是 RC4 薄封装(globalEnv.py:61),非 AES,故直接用 aes 模块。
  aes.py 第42/43行密文侧 iso-8859-1 必须保留(实测200组密文utf-8解码100%失败),
  只在调用方对明文侧做 UTF-8→latin-1 字节保真包装(中文/emoji/私钥 9/9 往返一致)。
- 占位符 @@sec:NAME@@(避开 sqlor ${X}$ 与 ahserver ArgsConvert 同语法冲突,
  也避开裸 $VAR 被 bash 就地展开)。
- 侦测三通道:已知前缀16种 / 关键词邻域+熵≥3.5 / 不做裸串侦测(防吞哈希数据)。
  中文冒号「apikey:」支持;_JUNK_VALUE_RE 拦占位示例值;低熵弱口令不误吞。
- 执行边界:占位符→$PIPELINE_SEC_NAME,真值走 subprocess env=(不用 bwrap
  --setenv——值进argv同机任意用户ps可读;env=落/proc/pid/environ仅属主可读)。
- 越权防护:get_secret_row(own_only=) 读写范围分离 + _assert_own 代码级二次校验
  (防任意机构成员覆盖/删除机构共享凭据)。写操作显式 COMMIT(多worker)。
- 审计只记 name/type,禁记值。

三路接入:
- gateway._secret_inbound_gate: run_message Step 0.5(所有上下文解析之前),
  progress 事件回显动作(体感不变但告知已自动保存)。
- agent_loop_v2: _t_secret_tool 六工具 + _t_run_command 执行边界(前台/后台分支
  之前) + _t_process 出站擦洗 + prompt 铁律(仅有 user_id 时注入)。
- agent_loop(v1角色agent): 只读三件(list/use/detect,无人值守禁自主增删凭据);
  身份=项目真人owner(resolve_project_owner,已处理 agent.pm 自动立项反查);
  run_shell 执行边界 + 出站擦洗。
- _run_shell/bg_jobs.start_bg_job 加 secret_env 参数(无secret时env=None零改动)。

本地测试 124 断言全绿(含三个挂调实锤陷阱回归:子串碰撞/占位符别名劫持/
未知占位符吞空 + 跨文件工具名同步 + 越权防护)。表定义 m0037(pipeline-app)。
2026-09-17 15:44:56 +08:00

306 lines
22 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# pipeline_service
产线执行引擎 —— 任务调度、步骤执行、人工任务交互、LLM 桥接。
## 功能
- **任务执行**DAG 步骤调度与状态机
- **人工任务**:审批/输入等待与交互
- **LLM 桥接**:统一 LLM 调用接口
- **Agent Loop**AI Agent 多轮任务执行
- **意图分类**:自然语言意图识别
- **产物管理**:步骤输入输出存储
## 数据表
| 表 | 说明 |
|---|------|
| pipeline_tasks | 任务实例 |
| pipeline_task_steps | 步骤执行记录 |
| pipeline_artifacts | 步骤产物 |
| pipeline_human_tasks | 人工任务 |
| pipeline_step_types | 步骤类型注册 |
| pipeline_session_settings | 会话设置(多 tab 项目上下文) |
| pipeline_conversations | 平台会话消息回放缓存2026-09-10 归籍本模块——唯一写入方 agent_loop_v2._save_turn此前是无 models 定义的孤儿表) |
| pipeline_agent_instances | 角色 agent 实例注册(项目×角色→稳定实例 id唯一写入方 agent_instance.pym0002 建表未进 models2026-09-10 归籍) |
| pipeline_agent_settings | 用户 agent 偏好(每用户一行 uk_user默认模型/当前项目指针;写入方 agent_loop_v2、set_agent_model.dspy、workspace.py2026-09-10 归籍) |
| pipeline_project_agents | 项目角色 agent 配置(驾驶舱 v1 start_agent 链路v2 已走 pipeline_agent_instances保留至 v1 链路删除决议2026-09-10 归籍) |
| pipeline_user_secrets | 用户敏感信息库2026-09-17m0037 建表AES 密文 + 指纹去重 + org/user 双维归属;写入方 secret_vault.pyagent 工具 + gateway 入站自动侦测);唯一索引 (org_id,user_id,name) |
## 安装
```bash
cd pkgs/pipeline-service && pip install .
```
## 核心模块
| 文件 | 职责 |
|------|------|
| `executor.py` | 任务/步骤调度引擎 |
| `storage.py` | 数据库读写 |
| `llm_bridge.py` | LLM API 调用 |
| `agent_loop.py` | AI Agent 多轮执行 |
| `human.py` | 人工任务处理 |
| `intent_classifier.py` | 意图识别 |
| `state.py` | 状态机 |
| `step_registry.py` | 步骤类型注册表 |
| `agent_loop_v2.py` | Agent Executor v2会话 agent 执行引擎) |
| `workspace.py` | 项目/通用会话工作空间目录管理 |
| `sdlc_ability.py` | SDLC 产线能力包(工具定义 + handler |
| `skill_live.py` | 技能实时发布/管理publish_skill_live + manage_skill_live 多租户隔离) |
| `bg_jobs.py` | run_command 后台任务(状态文件化 .bg/,跨 worker 可见) |
| `subagents.py` | 后台并行子 agent 委派(.sub/spawn/steer/stop/result |
| `diagram_gate.py` | 文档配图形态硬门禁2026-09-15交付文档/标书章节中的图必须 invoke_model t2i/i2i 真图,确定性检测 mermaid/plantuml/ASCII 伪图deliver 双路径拦截 + pipeline-bidding write/review_chapter 复用;豁免=文中如实标注「配图缺失:平台无可用文生图模型」 |
| `secret_vault.py` | 用户敏感信息库2026-09-17AES 密文入库 + 会话占位符引用 `@@sec:NAME@@` + 入站自动侦测入库;真值只在执行边界以子进程 env 注入,永不进模型上下文/落库/进命令字符串gateway 入站门禁 + v1 角色 agent项目 owner 身份)+ v2 会话 agent 三路接入 |
## 通用会话产线隔离2026-09-057 层纵深防御)
规范通用会话generic=Trueagent_chat_generic.dspy= 纯通用 agent
不挂任何产线工具/技能/角色/记忆,不得看到或操作任何产线的项目。
隔离层(缺一层即泄漏,详见技能 cockpit-agent-patterns
1. **工具注册表**ToolRegistry 每 executor 独立实例(禁全局单例,防跨会话残留)
2. **默认产线回退**generic 会话 pipeline_id 空不回退 DEFAULT_ABILITY_ID
3. **产线能力包**_execute_ability_tool 对 generic 直接拒绝
4. **工具 schema**load_agent_configpipeline-coregeneric 时剔除 project/shell 类工具
5. **执行层兜底**_dispatch_sdlc_tool 对 generic 拒绝项目管理工具(别名归一后拦截)
6. **工作空间**generic 会话文件根 = `_general/{user_id}` 专属目录generic_workspace_dir
_resolve_ws_path 越界保护圈死run_command 剔除shell 无法圈禁);
resolve_upload_dirpipeline-coregeneric=True 上传落同一专属目录
7. **历史回放**_load_history / _t_session_search 对 generic 只读写
`pipeline_id='' OR IS NULL` 的自身历史(与存库端 store_pl='' 对称),
禁止按 created_by 全量回放(会带出用户各产线对话中的项目信息)
## Hermes 能力对齐:写入侧 + 运行时侧2026-09-10
四个 handler 在 `agent_loop_v2.py`,工具定义在 pipeline-core `agent_config.py`
GENERAL_TOOLS。全部经测试机服务端直调 31/31 + HTTP 端到端 5/5 实测通过。
### 1. memory 工具(持久记忆写入,`_t_memory`
- 写入门禁代码强制多机构多用户scope 白名单 user/project/pipeline
global/org 种子域禁写org_id/user_id 强制注入会话真实身份(忽略 LLM 传值);
无 user_id无人值守拒写remove 只能删本人条目org+user 双过滤)。
- 记忆注入改可见性版(`_build_system_prompt` 传 org_id/user_id → store.visible_to
过滤),修了旧「按域叠加」会带出他机构归属记忆的隐患。
### 2. manage_skill 工具(技能增改删,`skill_live.manage_skill_live`
- 五动作 create/patch/write_file/remove_file/delete隔离全在 skill_live
只落本租户 orgs/{org}org≠0或 users/{uid}org 0 降级/个人);
- **fork-on-write**:改 global 原版先整目录继承拷贝到本租户再改(同名覆盖仅本租户
生效);**校验通过才 fork**(旧「先 fork 后校验」失败会留副本遮蔽原版,已修);
- org+user 双副本同步改user 遮蔽 org防改了被遮蔽那份产线/角色/项目层拒改;
delete 只删本租户副本global 删不到,删后重新可见);子文件限四目录 + realpath 双检。
### 3. run_command background + process 工具(`bg_jobs.py``_t_run_command`/`_t_process`
- 状态文件化 workspace/.bg/{job_id}/meta.json+output.log跨 worker/进程可见;
- 沙箱档位与前台一致generic 强制 strict bwrap无 bwrap 拒绝,绝不降级裸 shell
- start_new_session 独立进程组 + 超时 SIGTERM 整组(不留孤儿);心跳 stale 判活。
### 4. delegate_subtask background + subagent 工具(`subagents.py``_t_delegate_subtask`/`_t_subagent`
- 后台并行(同 workspace 上限 3状态文件化 .sub/{sid}/;深度限制 1子禁再委派
- 子会话 session_isolation='none':不读父历史、不写 pipeline_conversations防污染回放
- steer/stop 走文件传递,子 agent 每轮 tool-loop 边界 `subagents.heartbeat` 消费;
result.txt 流式落盘stop/崩溃即部分结果(绝不空手)。
## 第二批 Hermes 能力对齐:五级工具作用域 + patch_file + 原生视觉2026-09-10
### 五级作用域工具解析接线(`_resolve_tool_scopes`,解析逻辑在 core `tool_sources.py`
- `_init_components` 第 8 步:调 core 五级解析global/org/pipeline/role/project
registry 重建为解析后工具集capability 工具(技能 frontmatter 声明)合成
ToolDefinition 注册,执行路由 `capability_tools.exec_capability_tool`
- **执行层门禁**`_execute_tool` 第 0 步):`_allowed_tools` 非 None 时,作用域外
工具名(幻觉/越权)直接拒绝并回可行动提示(列出本会话可用工具)。解析失败
降级 `_allowed_tools=None` 不拦截(回退旧行为,不阻断会话)。
- 隔离走查修复capability 两层语义global 层 all/ 概念技能的 capability 声明
**不作为注入依据**(只当映射手册),否则 generic 会话拿到 ~70 个产线工具击穿
七层隔离;注入只认 org/pipeline/role/project/user 层技能的声明。
### patch_file 工具(`_t_patch_file`
old_string 唯一性校验0/多次拒绝replace_all 放行)+ 原子写 + `_resolve_ws_path`
越界防护 + UTF-8 校验(二进制拒绝)。
### 原生视觉(多模态消息链)
- `run(user_input, image_parts)`image_parts 非空时首条用户消息构造 OpenAI 多模态
content 数组text + image_url data URLllm_bridge→llm_v1 端点→inference 原样透传。
- `_call_llm` 降级兜底:含图调用失败 → `_degrade_images_if_needed` 剥离图片就地改写
消息(文本 + 系统说明「如实告知用户当前模型看不了图,建议切视觉模型或 invoke_model
i2t」重试一次——绝不假装看过图。只降级一次。
- `_estimate_tokens`/`_summarize` 兼容 list content`_content_as_text` 归一,图片按
1000 token 估)。
- `gateway.run_message(image_paths)`build_image_parts 构造 + 超限 notes 注入 content。
- 实测中性文件名红蓝两图agent 正确答「第一张红色、第二张蓝色」degrade 0 次。
## deliver 代码真实性三重门禁2026-09-16 pbls M1a 空壳事故根治)
**事故形状**develop agent 声称「已用 write_file 落盘 17141 字符、G1~G6 严格退出码门禁」,
实测交付的 verify_gate.py 只有 9 行纯注释 1225 字节——LLM 把「对文件的描述」当文件内容写入;
且 git 证据由 agent 自称实为引擎代收口。QC 抓到时代价已是 4 轮烧光 + fault + pause。
三层确定性门禁(全部引擎层,不靠 LLM 自觉):
1. **git 收口引擎化**38efb3edeliver 时引擎代为 add+commit 本任务写入的
apps/modules 仓库,收口核验记录(改了哪些仓库/几个文件/commit hash回填交付件正文——
agent 无法再自称「已提交」伪造 git 证据。
2. **代码空壳硬门禁 `_validate_stub_code`**0d02ac6deliver 时本任务 write_file
实写的每个 `.py` 必须 ast 可解析且有真实可执行语句(纯注释/pass/纯 docstring/
语法错误一律拒绝,`__init__.py`/`conftest.py` 豁免),`.json` 必须可解析;
`files` 参数先验后写(`_validate_code_files_params`),防空壳落盘后被收口变既成事实。
拒绝时回填可行动 FAIL指出文件+原因agent 当轮重写——空壳死在 deliver 入口,
不再烧 QC 轮次。native + 文本兜底两条 deliver 路径都挂载。
3. **产出文件机械核验段**:引擎自动计算每个代码产出文件的实测字节/行数/语法/语句数,
以「产出文件机械核验(引擎自动计算,非 agent 声明)」标题回填交付件正文——
「声称 17141 实测 1225」类矛盾一眼可见QC 拿到引擎级证据底座,
不再依赖自己 run_shell 逐个取证(取证失败盲判 0 分退也在烧重试预算,实测 9 轮里 3 轮是盲审)。
配套技能pipeline-core 7bfa8d6develop 角色「代码真实性铁律」write_file 必须完整正文、
大文件拆小分批、声称数字须与引擎核验一致、勿自称 commit/pushQC review-develop
「优先读引擎回填两段核验当权威证据;声称 vs 实测矛盾直接按造假退;取证失败禁盲判 0 分,
改 read_file 逐项或 ask_question 冒泡」。
**通用规则**:质量门禁的判定证据必须来自引擎机械计算(字节/行数/ast/commit hash
不能来自 agent 的自我声明QC 尺子要配「取证失败 ≠ 判 0 分」的出路,否则盲审烧轮次。
## LLM 超时预算统一治理2026-09-16 用户裁定:单点收敛,禁散落)
**事故背景**09-05 提取、09-14 develop、09-16 QC 三次事故同一根因——调用方忘传
timeout → payload 无 _timeout → inference 按供应商端点配置(百炼 120s掐断长生成
且超时公式散落在各产线各角色调用点570/330/+60/-60/min+60 各自为政),按角色
逐个修必然复发09-14 只修 develop native09-16 QC 复发实锤)。
**统一架构(全平台只有两处单点)**
1. `llm_bridge._resolve_budget(timeout, default)`timeout 语义=一次调用【总预算】
0=平台缺省 chat 300 / 生成类 900封顶 900 下限 60→ 返回
(payload._timeout=总预算, 客户端 aiohttp=总预算+60)。四个公开入口
llm_call/llm_call_msgs/llm_call_msgs_native/llm_infer全走它
payload._timeout 恒传——忘传 timeout 也结构性安全。
2. `pipeline-llm inference._post_upstream` deadlineattempt 超时=剩余预算,
总时长≈预算(消灭 3× 放大→客户端先断连→模糊 TimeoutError→误判永久错误链
预算耗尽(<15s)不再发起必然超时 attempt端点配置 timeout 不再掐断 attempt
仅直连未传 _timeout 时与 _TOTAL_TIMEOUT 取大兜底)。
**三层预算对齐铁律**调用方总预算 T payload._timeout=T 客户端=T+60
外层 wait_for若有)≥ T+60agent_loop510570600 实证对齐
验证特征串inference 日志预算=Ns」llm_call_trace status_code=0 计数
新增 LLM 调用点只需可选声明总预算禁止自算客户端等待
## 用户敏感信息库secret_vault2026-09-17
**要解决的问题**用户在会话里直接贴 apikey/token 明文会进模型上下文 随每轮 API
调用完整上行供应商 `pipeline_conversations` 永久留存 session_search 捞回再上行
Hermes 侧同源分析实测一个 5 轮任务的 key 上行 3~5 含重试可翻倍。)
**设计不变量(写死在代码,不靠 LLM 自觉)**
1. **真值只以子进程环境变量形式存在**绝不拼进命令字符串占位符 `@@sec:NAME@@`
在执行边界转成 `$PIPELINE_SEC_NAME`值经 `subprocess env=` 注入
命令原文可安全落库/进上下文模型抄回占位符是**预期行为**不像脱敏成 `***`
会让模型把占位符抄进命令导致任务全断)。
2. **env 注入走 `env=`,不用 bwrap `--setenv`**后者把值放进 bwrap argv同机任意
用户 `ps aux` / `/proc/<pid>/cmdline` 可读world-readable`env=` 落在
`/proc/<pid>/environ`仅属主与 root 可读bwrap 未用 `--clearenv`默认继承自身环境
所以 `env=` 的值能进沙箱内
3. **未知占位符原样保留 + 拒绝执行**绝不替换成空串空串会静默改变命令语义
实测 `rm -rf ${TARGET_DIR}$/` `rm -rf /`)。
4. **读写权限范围分离**`get_secret_row(own_only=)`/用路径含机构共享条目
`user_id=''`共享凭据就是给成员用的写路径save/delete/set_status只匹配本人
条目 + `_assert_own` 代码级二次校验否则任意机构成员可覆盖/删除全机构共用凭据
5. **入站侦测只吃高置信度**已知凭据前缀16 或关键词邻域 + 3.5
中低置信度只提示不自动入库——误报会把哈希/base64 数据块当凭据吞掉静默毁数据
比漏报更糟。**不做无上下文裸串侦测**同理)。
**占位符语法为什么是 `@@sec:NAME@@`**
不能用 `${X}$`sqlor 参数化`sor.py:78` `sqlvp='${', sqlvs='}$'` ahserver
`baseProcessor.py:193` `ArgsConvert("${","}$")` 都用这个语法复用会被误展开
也不能用裸 `$VAR`bash 会就地展开绕过执行边界
**加密AESappPublic.aes不是 password_encode**
`ahserver.globalEnv.password_encode/decode`:255-261实测是 **RC4** 薄封装
`:61 from appPublic.rc4 import password, unpassword`换过去等于没换
真正的 AES `appPublic.aes`AES-ECB + PKCS7 + base64 DB 连接密码同套
`aes.py` 内有**两处方向相反** iso-8859-1只有一处需要包装
| 位置 | 方向 | 结论 |
|---|---|---|
| `aes.py:42-43,46-47` | 密文 bytes str | **必须** iso-8859-1实测 200 AES 密文用 utf-8 解码失败 200 100%首字节 0xef invalid continuation byte)——密文是二进制不符合 UTF-8 规范历史正确设计**不要动** |
| `aes.py:29,37` | 明文 str bytes | iso-8859-1 表示不了非 ASCII实测密码是中文Abc123」「🔑key123直接 UnicodeEncodeError |
解法在**调用方**包装不改 aes.py——appPublic 是多宿主共享基础模块改它影响 DB 密码
等全部调用方`str → UTF-8 bytes → 按 latin-1 逐字节还原成等长 str 传入`latin-1
字节保真映射0x00-0xFF 一一对应`aes.py:29` `encode('iso-8859-1')` 即还原成原始
UTF-8 字节流 = 字节保真空操作。出库反向还原。包装后实测 9/9 往返一致ASCII / GitHub
PAT / 中文 / emoji / 中英混合+符号 / 私钥含换行 / 512 长值 / `p@$$w0rd!#%^&*()` /
`@@sec:` 语法的值密文零明文残留
密文带版本前缀 `AES1$`base64 密文无固定形态特征无法像 RC4 `QUZVcX` 靠前缀嗅探
判断是否已加密显式版本标记同时为算法迁移留路`looks_encrypted()` 同时识别 `AES1$`
与历史 `QUZVcX`幂等加密防多层叠加 + 向后兼容读取 llm 表等存量 RC4 密文)。
**三路接入**
| 路径 | 位置 | 身份来源 | 工具集 |
|---|---|---|---|
| 入站门禁 | `gateway.run_message` Step 0.5所有上下文解析**之前** | `get_user()` | 自动侦测入库替换progress 事件回显动作 |
| v2 会话 agent | `agent_loop_v2._t_secret_tool` + `_t_run_command` + `_t_process` | `self.user_id`作用域解析里被 generic 置空的那份**不能用** | 全六件 |
| v1 角色 agent | `agent_loop._exec_agent_tool` + `run_shell` 分支 | 项目真人 owner`rag_client.resolve_project_owner`已处理 `created_by='agent.pm'` 自动立项反查 | 只读三件list/use/detect)——无人值守下 LLM 自主增删凭据风险高于收益 |
入站门禁必须在 Step 0.5净化后的 content 才是该进模型上下文与落库的版本
明文一旦进 executor 就会每轮上行供应商
出站净化两处`_t_run_command`前台+ `_t_process`包一层 `_t_process_raw`
后台 output.log 可能回显凭据)。执行边界必须在前台/后台分支**之前**否则后台命令不受保护
**前端AgentIO 钥匙图标)**
bricks 是多应用共用框架**不硬编码任何宿主路径或占位符格式**对齐 `model_dataurl`
既有设计)。宿主通过 opts 注入 `secret_dataurl` / `secret_format` / `secret_title` /
`secret_tip`未传 `secret_dataurl` 时钥匙图标根本不渲染 其他应用零影响
- `UiText.insertAtCursor(text)`光标位置插入有选区则替换选区两个时序坑照抄
`handle_enter`/`handle_tab_indent` 既有处理程序改 `dom_element.value` 不触发 input
事件须手动同步 `this.value``focus()` 后浏览器可能重置 selectionStart
`schedule_once` 0.5ms 后重设)。** dispatch('changed')**——UiText 也用于 Form
多发一次会触发脏值/校验联动
- 弹窗 API`PopupWindow` `close()` 也无 `'closed'` 事件正确是 `dismiss()` +
`'dismissed'`popup.js:475/481)。
- 样式走全局 css class`.secret-picker-row` )——bricks 内联嵌套 style 对象无效
- 6 AgentIO 入口全部注入`pipeline-core/wwwroot/agent/index.ui`静态 + 动态建 tab
脚本各一处)、`agent_generic/index.ui``mobile_agent.ui`2 )、
`pipeline-sdlc/wwwroot/index.ui``index_cockpit.ui`
- 后端 `pipeline-core/wwwroot/api/secret_options.dspy`身份取自 `get_user()`/`get_userorgid()`
**不接受前端传 user_id/org_id**否则改参数即可枚举他人凭据名称双保险剔除
`encrypted_value`/`fingerprint`未登录返 `success:false`区分没登录没数据」)。
RBAC 已被 `rp.json` `/pipeline_core/**`(logined) 覆盖无需额外注册
**性能(实测,本地 20KB 文本)**
`tokenize` 0.003ms / `detokenize` 0.016ms / 4KB 侦测 0.64ms / 用户消息侦测 0.034ms
**三个挂调实锤的实现陷阱(勿回退,有回归测试)**
| 陷阱 | 症状 | 修复 |
|---|---|---|
| 子串碰撞 | key `key-Ab3x` 与长 key 共存时按插入序替换 key 被吃掉前缀尾部明文裸露进上下文 | `_sorted_secrets` 按值长度**降序**替换 |
| 占位符别名劫持 | secret 的值本身长得像占位符 二次 token 化改写既有占位符 | 入库拒绝值内含 `@@sec:``_PLACEHOLDER_IN_VALUE`+ tokenize 幂等 |
| 未知占位符吞空 | `ArgsConvert(default='')` 把缺失变量替换成空串 命令语义反转 | `detokenize` 原样保留 + 回报未知名单执行边界拒绝执行 |
另两个自产 bug`_PLACEHOLDER_RE` `{1,62}`最少 2 字符 `_NAME_RE` 长度下界不一致
单字符名存得进库但识别不出未知占位符漏报`guess_type` 名称关键词优先
`openai_style_key` 里的 `key` 把精确类型降级成 `api_key`均已修 + 加同步断言
**测试**`/tmp/test_secret_vault.py` 124 断言全绿11 AES 加解密/子串碰撞/别名劫持/
未知占位符/侦测/名称归一/端到端/性能/DB /跨文件工具名同步/越权防护)。
`CapturingSor` **必须真实模拟 WHERE 过滤**——第一版对任何 SELECT 都返回全部 rows
无视 SQL 条件导致零写操作断言在过滤正确时反而 FAIL假阳性)。
测试桩失真 = 测试证明不了任何事。
**部署**`dist/` bricks .gitignore 构建产物不入库)→ 部署必须在测试机
`cd pkgs/bricks && bash build.sh` 重建不能靠 git pullbricks 追平必须连 dist 重建」)。
建表走 m0037 + `create_tables.py`自动扫描 `pkgs/*/models`索引按列集合幂等判重)。