Files
patbond-ai/README.md
T
lixi 4762358f54
CI / ai-gates (push) Failing after 29s
fix(security): 凭证扫描放行依赖注入传参 + 四仓口径更正
`api_key=volcengine.api_key` 这类**依赖注入传参**被 KEY-ASSIGN 判为疑似凭证。
这是规则的真缺口而非代码问题:行里没有任何秘密,而做 DI 的代码到处都是这种
写法——绕过一次就得在每个调用点绕过,那等于把规则废掉。故改规则。

新增 ALLOW_REFERENCE,刻意收得很紧以免开后门:
- 值必须是**未加引号**的小写起首标识符引用(真密钥要么带引号,要么不是标识符形状)
- 只允许**恰好一个点且随即行尾**
- 连字符不在字符集内

对抗性夹具验证(三真三假):
- 拦下 `api_key = "sk-T3Blbk…"`(OpenAI 形态)
- 拦下 `secret_key: "AKID…"`(腾讯云形态)
- 拦下 `auth_token=eyJ….eyJ….Sfl…`(**三段式 JWT——两个点,故不落进放行**)
- 放行 `api_key=lowercase.reference` / `api_key = settings.api_key` / `api_token: config.authToken`
原 5 形态泄漏夹具复验仍 8 条命中,一条不少。

另更正口径:脚本头与 git-workflow.md 仍写「三仓同构」,实际已是四仓——
patbond-ai 是唯一持有 AI 服务商凭证的仓,漏同步它风险最大。四仓 md5 已一致
(4945940093b4b8413b7d3eec8bf6a5ec),四仓 --all 全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 12:18:18 +08:00

129 lines
6.4 KiB
Markdown
Raw 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.
# patbond-ai
Patbond 的 AI 生成服务(Python)。负责调用 AI 服务商 API 完成图片与视频生成,
是四仓中唯一直接持有 AI 服务商凭证的仓库。
架构与边界的完整决策见 **patbond-doc `docs/architecture/decisions.md` ADR-023**。
以下为要点,改动前请以 ADR 原文为准。
## 职责边界
- **只负责调用 AI 服务商 API。** 不访问数据库——生成任务状态的唯一事实来源是
Java 侧的 `creation.generation_jobs`。
- **不持有对象存储凭证。** 产物由 Java 下发预签名 PUT 地址,本服务直传后回报,
再由 Java 核实并建 `media.assets` 行(即客户端两步上传,本服务站在客户端位置)。
- **对上游同步或异步一律对外呈现为异步**:`submit` 立即返回任务 id,同步型
服务商(长连接等待返回)在本服务内部以后台任务承载。这样 Java 侧无长连接占线程,
且 `provider_request_id` 立即可得,超时不再产生「是否已扣费」的歧义。
## 与 Java 侧的接口
- 传输为 **REST(JSON)**,非 gRPC(理由见 ADR-023 §4:避免引入第二套无门禁契约体系)。
- 契约为本仓内独立的 **`openapi-internal.yaml`**,独立版本号,
**不进** patbond-api 的四模块快照锁,**也不写入**面向客户端的 `openapi.yaml`。
- 鉴权为 **服务身份 JWT**(独立密钥对,非用户 token 转发):本服务强制校验
`aud=patbond-ai` 与 `typ=service`。
- Java 主动轮询本服务查询进度;回调是将来的可选优化,当前不启用。
- 请求体**不含 `userId`**(数据最小化):完成工作只需 `jobId`、生成参数与预签名上传地址。
## 代码结构
分四层,依赖**单向**向内(`config` → `providers` → `domain`),由
`tests/test_layering.py` 自动守着——靠纪律守不住的东西,靠测试才守得住。
```
app/
├── domain/generation.py 与服务商无关的领域模型
├── providers/
│ ├── base.py 生成能力抽象(新增供应商 = 实现它)
│ ├── registry.py provider_code → 适配器
│ └── volcengine/
│ ├── client.py HTTP 传输与鉴权(不认识「视频」)
│ ├── errors.py 火山错误码 → 领域 Failure
│ ├── models.py 厂商请求/响应 DTO
│ └── video.py Seedance 视频业务
├── config.py 集中配置(全部旋钮从环境变量读)
└── assembly.py 装配根(唯一同时知道配置与具体供应商的地方)
```
**为什么这么切**:
- **`domain/` 对厂商与 httpx 零依赖。** 这是「换供应商不动上层」的支点。判断一个
概念该放哪:问「换一家供应商,它还在吗」——`Retry`/`UserFacing` 描述的是
Worker 与客户端该怎么做,与谁家无关,故在领域层;`expired` 状态、
`QuotaExceeded` 这类码是火山特有的,故在适配层。
- **`client.py` 与 `video.py` 分开。** client 只管鉴权、超时、错误解包,不认识
「视频」。以后接火山的**图片或语音**接口时新建兄弟模块复用同一个 client,
而不是再抄一遍鉴权与错误解包——这是拆它出来的全部理由。
- **适配层不读配置**,全部通过构造参数注入。否则单测得摆弄环境变量,且同一进程
内没法跑两套不同配置的实例。
### 新增一家供应商 / 一种能力
1. 实现 `app/providers/base.py` 的 `GenerationProvider`;
2. 在 `app/assembly.py` 的 `build_registry()` 里登记一行;
3. 目录表的 `provider_code` 与适配器的 `provider_code` 必须同值——Java 侧按
任务行冻结的 `provider_code_snapshot` 查适配器(**不按当前目录**,因为目录
可能被改指向,而在途任务必须仍由当初提交它的适配器轮询)。
上层与领域层**一行都不用改**。
## 已接入的服务商
| 服务商 | 能力 | provider_code | 状态 |
| --- | --- | --- | --- |
| 火山方舟 Seedance | 视频生成 | `volcengine-seedance` | 提交与查询已实现,产物转存待存储决策 |
接口事实与错误码映射的完整取证见 patbond-doc
`docs/development/providers/volcengine-seedance.md`。其中两条尤其要记住:
- **火山不支持幂等键**,「提交超时后不知道有没有扣费」只能由 `max_attempts` 兜住;
- **产物 URL 只有 24 小时有效期**,因此厂商 URL 不能当存储用,必须在时限内转存。
## 配置
全部旋钮见 `app/config.py`,常用项:
| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `ARK_API_KEY` | _(无)_ | 火山方舟密钥。**缺失时该适配器不装配**,服务仍可启动 |
| `ARK_BASE_URL` | 北京区官方地址 | |
| `ARK_TIMEOUT_SEC` | `10` | **单次 HTTP 往返**超时,不是生成耗时 |
| `ARK_VIDEO_MODEL` | _(无)_ | 仅作兜底;正常由 Java 侧传入任务行的模型快照 |
| `ARK_TASK_EXPIRY_SEC` | `3600` | 厂商 `execution_expires_after`,区间 `[3600, 259200]`。默认取下限——厂商默认 48 小时对交互式应用过长 |
| `PATBOND_SERVICE_JWT_PUBLIC_KEY` | _(无)_ | 校验 Java 侧服务票的 RS256 公钥 |
| `PATBOND_SERVICE_JWT_AUDIENCE` | `patbond-ai` | **必须强制校验**,否则独立密钥对失去意义 |
非法值**响亮失败而不静默回落**:一次配置笔误变成难查的行为偏差是最糟的结果。
## 本地门禁
与 CI 同一套命令(`.gitea/workflows/ci.yml`):
```bash
sh scripts/check-secrets.sh --all # 凭证防泄漏全量自查
ruff format --check .
ruff check .
pytest -q
```
**每人每仓启用一次** pre-commit 防泄漏钩子:
```bash
git config core.hooksPath scripts/hooks
```
`scripts/check-secrets.sh` 是四仓同构副本,**调整规则时四仓同步提交**。
它覆盖 AI 服务商密钥形态(`sk-ant-`、`sk-`/`sk-proj-`/`sk-svcacct-`)与
`api_key`/`auth_token` 实值赋值——本仓正是这些规则的主要适用对象。
## 凭证纪律
真实密钥**只允许存在于被 gitignore 的文件中**(`.env`),占位样例进 `.env.sample`。
拦下真实凭证时的第一动作是**去服务商控制台轮换/禁用该密钥**,然后才是清理提交。
## 分支与发布
`dev` 直推 + CI 绿,`main` 经 PR 合并。本仓纳入四仓 tag 同步与发布回归
(ADR-023 §12)。发布流程见 patbond-doc `docs/development/releases.md`。