Make the offline RAG chain observable without widening credential access
Some checks failed
verify / verify (push) Has been cancelled

Expose the verified synthetic retrieval path through a typed React client and a non-root Nginx edge while keeping database credentials and data-network reachability out of the browser tier. A fixed-origin gateway preserves request boundaries and now closes upstream streams even when downstream disconnects before body iteration. The deployment ADR and runbooks record the four-network topology and its accepted Web edge-egress risk.

Constraint: The previously exposed Bailian key must be revoked and no live provider credential may enter Git, images, logs, or the browser.
Rejected: Connect Web directly to the data network | expands lateral reach to PostgreSQL.
Rejected: Publish the database-aware API on the edge network | gives a credential-bearing process a public default route.
Rejected: Buffer streaming responses in either proxy | prevents incremental chat delivery in the future.
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Do not mark Stage 1 or Stage 2 complete until the rotated-key live smoke and remaining stage gates pass.
Tested: make verify; 65 backend tests; 14 frontend tests; Docker image build; container health and isolation probes; real HTTP demo/status/search/docs/OpenAPI checks.
Not-tested: Live Bailian models, real document ingestion, business chat SSE generation, and final browser screenshot automation because the browser skill runtime was unavailable.
This commit is contained in:
2026-07-12 17:38:57 +08:00
parent 2fa27ae71c
commit c3bad0f186
60 changed files with 9062 additions and 82 deletions

View File

@@ -132,7 +132,8 @@ deepseek-v4-flash
```mermaid
flowchart LR
U["浏览器用户"] -->|"HTTPS / SSE"| W["React + Nginx"]
W -->|"/api/v1"| A["FastAPI API"]
W -->|"同源 /api"| G["无 Secret 固定上游 Gateway"]
G -->|"internal data"| A["FastAPI API"]
A --> D[("PostgreSQL + pgvector")]
A --> F[("文件卷 / OSS 适配器")]
A --> E["text-embedding-v4"]
@@ -150,6 +151,7 @@ flowchart LR
| 组件 | 职责 | 不承担 |
|---|---|---|
| `web` | React 静态页面、Nginx 代理、SSE 透传 | 模型凭证、业务权限判断 |
| `gateway` | 固定 API 上游、请求大小/头边界、脱敏错误和流式转发 | 数据库凭证、模型凭证、业务权限判断 |
| `api` | 认证、知识库、检索、问答、评测 API | 长时间 OCR/批量入库 |
| `worker` | 解析、OCR、分块、向量化、索引、评测批任务 | 对外开放端口 |
| `db` | 元数据、向量、任务、会话、评测事实来源 | 原始大文件长期存储 |

View File

@@ -29,9 +29,10 @@ docker compose up -d --build
|---|---|---|---|
| `db` | 固定版本 pgvector 镜像 | 元数据、向量、任务、会话、评测 | 仅内部网络 |
| `migrate` | 与后端同镜像 | 运行 Alembic一次成功退出 | 无 |
| `api` | `backend/Dockerfile` | FastAPI、检索、问答、管理 API | 仅由 Web 代理 |
| `api` | `backend/Dockerfile` | FastAPI、检索、问答、管理 API | 仅 internal data 网络 |
| `gateway` | 与后端同镜像 | 固定上游、请求边界、脱敏错误和流式转发 | 仅 internal ingress/data 网络 |
| `worker` | 同一后端镜像 | 解析、向量化、评测后台任务 | 无 |
| `web` | `frontend/Dockerfile` | Nginx 静态资源和反向代理 | 宿主机 8080/HTTPS |
| `web` | `frontend/Dockerfile` | React 静态资源、Nginx 同源入口和 SSE 代理 | `127.0.0.1:8000`;生产部署 HTTPS |
| `ocr-worker` | 可选 profile | PaddleOCR 重型任务 | 无 |
| `prometheus/grafana` | 可选 profile | 本地观测 | 默认不对公网 |
@@ -116,26 +117,31 @@ services:
api:
build: ./backend
command: ["python", "-m", "app.main"]
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
depends_on:
migrate:
condition: service_completed_successfully
environment:
<<: [*runtime-config, *rag-config]
APP_SECRET_KEY_FILE: /run/secrets/app_secret_key
environment: *runtime-config
secrets:
- postgres_app_password
- app_secret_key
- bailian_api_key
volumes:
- uploads:/data/uploads
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health/ready', timeout=2)"]
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=2)"]
interval: 10s
timeout: 3s
retries: 12
start_period: 10s
networks: [edge, data, egress]
networks: [data]
restart: unless-stopped
gateway:
build: ./backend
command: ["uvicorn", "app.gateway:app", "--host", "0.0.0.0", "--port", "8000"]
depends_on:
api:
condition: service_healthy
networks: [ingress, data]
restart: unless-stopped
worker:
@@ -183,11 +189,11 @@ services:
web:
build: ./frontend
depends_on:
api:
gateway:
condition: service_healthy
ports:
- "8080:80"
networks: [edge]
- "127.0.0.1:8000:8080"
networks: [edge, ingress]
restart: unless-stopped
volumes:
@@ -197,6 +203,9 @@ volumes:
networks:
edge:
driver: bridge
ingress:
driver: bridge
internal: true
data:
driver: bridge
internal: true
@@ -210,15 +219,13 @@ secrets:
file: ./secrets/postgres_migrator_password
postgres_app_password:
file: ./secrets/postgres_app_password
app_secret_key:
file: ./secrets/app_secret_key
bailian_api_key:
file: ./secrets/bailian_api_key
```
最终实现固定镜像 digest示意版本只说明可行结构。`migrate` 只有一个实例执行API 和 Worker 不在启动时并发自动迁移。首次初始化脚本只在 `db` 容器内使用 bootstrap 凭据创建 `vector` 扩展、无超级用户权限的 migrator/app 登录角色和专属 schemamigrator 拥有 schema/DDLapp 仅获运行期所需的 DML/sequence 权限及默认权限API/Worker 永远不挂载 bootstrap 或 migrator Secret。最后一个初始化脚本只有在全部 SQL 事务和授权成功后,才在 `PGDATA` 内用同文件系统 rename 原子写入 `.rag-bootstrap-complete`healthcheck 同时检查 `pg_isready` 与该哨兵,防止临时初始化 server 提前放行迁移。备份再使用独立只读角色。Compose 的 `.env` 仅做变量插值,不会自动注入容器,因此可配置的非敏感项和 `*_FILE` 路径通过 YAML anchor 在 `environment` 中完整声明。后端从各自的 `POSTGRES_PASSWORD_FILE` 读取密码后在内存中组装 DSN不把明文密码放进 `.env``DATABASE_URL`
网络分为层:`web + api` 位于 `edge``api + worker + migrate + db` 位于 `data`只有 `api + worker` 位于 `egress``data` 设置 `internal: true`,所以 Web 无法横向连接数据库;普通 bridge 本身不构成出站域名白名单,生产机仍需主机防火墙/出口代理放行百炼和对象存储域名。
网络分为层:`web` 位于普通 `edge` 和 internal `ingress``gateway` 位于 `ingress + data`API、迁移、Worker 和数据库位于 internal `data`只有明确需要云调用的一次性工具或未来 Worker 才能加入命名的 `egress`。因此浏览器入口不能横向连接数据库,数据库感知 API 也没有公网默认出口。只有无 Secret 的 Web 容器发布回环端口gateway 固定上游并继续执行请求边界和脱敏错误契约。详细理由和被否决方案见 [ADR-0004](adr/0004-secretless-web-ingress.md)。必须注意:普通 `edge` bridge 为 Web 提供宿主机端口发布时,也给 Web 留有技术上的默认公网出口;当前接受这一点的前提是 Web 无 Secret、无数据网络、无挂载且代理上游固定。若部署基线要求入口容器也完全禁网必须在宿主机防火墙/出口代理层阻断,不能把“未加入名为 egress 的网络”误写成“没有外网出口”。普通 bridge 本身不构成域名白名单,正式联网 Worker 仍需主机防火墙出口代理放行百炼和对象存储域名。
### 2.3 后台任务为何不用 Redis

View File

@@ -7,7 +7,7 @@
| 已完成阶段 | Stage 0仓库、安全和设计基线 |
| 整体完成度 | 约 12%,合理区间 10%15% |
| 设计完成度 | 设计基线已完成;实现中发现新约束时继续通过 ADR 和文档修订维护 |
| 业务代码完成度 | 约 10%Stage 1 的 Docker API、离线检索数据库 PoC 已验收真实百炼 smoke 仍待新 Key,前端产品功能尚未实现 |
| 业务代码完成度 | 约 18%Stage 1 的 Docker API、离线检索数据库 PoC,以及 React 离线演示、Nginx 单入口和四网络隔离前置均已验收真实百炼 smoke 仍待轮换后的新 Key |
| 当前预计剩余工期 | 约 913 周,含 300 题正式标注、盲测、论文和答辩缓冲 |
| 进度权威来源 | 本文的阶段状态、验收证据和已推送提交 |
@@ -50,7 +50,7 @@
| Stage 9 答辩、发布与归档 | 3% | `TODO` | 0% |
| **合计** | **100%** | — | **12%** |
Stage 1 已完成可离线验收的数据库、适配器契约、synthetic seed只读 API 子闭环,阶段内部约完成 90%;但真实三模型 smoke 仍依赖轮换后的新 Key因此 Stage 1 不提前计入整体里程碑。本文复选框表示“已通过验收”,不表示“文件是否已经出现”。当前可诚实表述为:**设计基线完成,整体里程碑约 12%Stage 1 离线后端可运行,真实百炼验证尚未完成。**
Stage 1 已完成可离线验收的数据库、适配器契约、synthetic seed只读 API 和 Web 演示子闭环,安全离线子阶段内部约完成 95%;但真实三模型 smoke 仍依赖轮换后的新 Key因此 Stage 1 不提前计入整体里程碑。Stage 2 中不依赖云密钥的 React、Nginx、网络隔离和质量门禁前置已经完成但 Worker 租约、完整产品工作流等必选项尚未完成Stage 2 仍为 `TODO`本文复选框表示“已通过验收”,不表示“文件是否已经出现”。当前可诚实表述为:**设计基线完成,整体里程碑约 12%业务代码约 18%Stage 1 安全离线链路可运行,真实百炼验证尚未完成。**
## 2. 阶段依赖与关键路径
@@ -70,7 +70,7 @@ Stage 0 DONE
| 阶段 | 必须依赖 | 可提前并行的工作 | 不得提前做的工作 |
|---|---|---|---|
| Stage 1 | Stage 0、已轮换 Secret、受控联网 | 虚构数据和 fake server 契约测试 | 使用真实地质资料,或使用已暴露密钥 |
| Stage 2 | Stage 1 三模型和迁移 PoC | 前端静态布局、OpenAPI 生成脚本 | 绕过任务租约直接做不可恢复长任务 |
| Stage 2 | Stage 1 三模型和迁移 PoC | React 离线演示、OpenAPI 类型生成、Nginx 单入口和网络隔离前置 | 绕过任务租约直接做不可恢复长任务 |
| Stage 3 | Stage 2 服务骨架 | 合法语料清单、标注规范准备 | 未通过 `CLOUD_APPROVED` 的内容出域 |
| Stage 4 | Stage 3 可追溯切片与向量 | 100 组三元组完善 | 用测试集调相似度、Top K 或阈值 |
| Stage 5 | Stage 4 稳定候选和引用 ID | UI 组件、Prompt fake 测试 | 让前端直接接触模型 Key |
@@ -165,7 +165,7 @@ Stage 0 DONE
- [x] `docker compose run --rm seed-demo-offline` 完成 20 条虚构数据写入、检索和重排;真实模式待新 Key。
- [x] `api + gateway` 镜像构建并达到 healthylive/ready/meta/demo status/demo search 均通过真实 HTTP 验收。
- [x] 第二次运行 seed 后数据库计数与第一次相同,均为 chunks/vectors/searchable = 20/20/20。
- [x] 63 项测试证明模型契约、健康检查、固定上游 gateway、离线 demo、数据治理和失败路径正确。
- [x] 65后端测试证明模型契约、健康检查、固定上游 gateway、SSE 代理分块与早断连释放契约、离线 demo、数据治理和失败路径正确。
- [x] `make verify`、Secret 扫描、固定镜像构建和 `git diff --check` 通过。
### 2026-07-12 已验证运行证据
@@ -177,6 +177,10 @@ Stage 0 DONE
- API 容器对公网 TCP 探测返回 `ENETUNREACH (101)`gateway 的 mounts 为空且不含数据库或百炼业务环境变量。
- PostgreSQL 重启后计数仍为 20/20/20。
- app 尝试修改已 `CLOUD_APPROVED``cloud_text` 被审批不可变触发器拒绝;尝试建表被权限拒绝。
- React + TypeScript strict 离线演示已实现工作台与系统状态页14 项前端测试、ESLint、类型检查和生产构建通过。
- Nginx 成为唯一回环入口Compose 仅发布 `127.0.0.1:8000``web -> gateway -> api` 同源链路、Swagger、OpenAPI、健康检查和 synthetic search 均通过真实 HTTP 验收。
- `edge / ingress / data / egress` 四网络边界已落地Web 不可解析数据库API 与 gateway 无公网 TCP 出口Web 与 gateway 均不持有 Secret。
- gateway 与 Nginx 已关闭代理缓冲,自动测试证明两个 SSE 事件保持为独立下游分块,且响应头阶段断连也会释放上游流;真实聊天生成与取消传播仍属于 Stage 5尚未实现。
- 唯一未验收项是真实 `text-embedding-v4` / `qwen3-rerank` / `deepseek-v4-flash` smoke 与真实模式 seed。
### 提交/推送节点
@@ -202,6 +206,7 @@ Stage 0 DONE
- **状态:** `TODO`
- **预计工期:** 34 个工作日
- **依赖:** Stage 1 `DONE`
- **已完成前置:** 不依赖云密钥的 React 离线演示、OpenAPI 类型、Nginx 单入口、四网络隔离和当前质量门禁已验收;这不代表 Stage 2 已启动或完成。
### 具体实现任务
@@ -211,26 +216,26 @@ Stage 0 DONE
- [ ] 建立 PostgreSQL `background_jobs`、租约、lease token、fencing 和重试状态机。
- [ ] 建立 Worker maintenance loop、advisory-lock reaper 和强杀恢复测试。
- [ ] 建立 `/health/live``/health/ready` 和管理员模型能力探测端点。
- [ ] 建立 React + TypeScript strict、路由、TanStack Query 和 OpenAPI 客户端生成。
- [ ] 建立 Nginx仅发布 Web 端口;API、Worker、DB 只在内部网络
- [x] 建立 React + TypeScript strict、路由、TanStack Query 和 OpenAPI 客户端生成。
- [x] 建立 Nginx仅发布 Web 端口;当前 API、DB 只在内部网络Worker 落地后必须沿用同一边界
- [ ] 建立完整 Compose`web/api/worker/db/migrate`OCR profile 默认关闭。
- [ ] 建立 Ruff、类型、pytest、ESLint、TypeScript、Vitest 和镜像检查。
- [ ] 建立 Gitea CI 的 fake provider 测试;外部 PR 不运行真实 Key 测试。
- [x] 建立当前范围的 Ruff、类型、pytest、ESLint、TypeScript、Vitest 和镜像检查Stage 2 新模块必须继续纳入门禁
- [x] 建立 Gitea CI 的 fake provider 测试;外部 PR 不运行真实 Key 测试。
### 验收证据
- [ ] 空白机器仅按 README 执行即可启动完整骨架。
- [ ] migration 只执行一次,失败时 API readiness 不通过。
- [ ] `docker compose ps` 只暴露 Nginx/Web 端口
- [x] 当前 `docker compose ps` 只暴露 Nginx/Web 回环端口;加入 Worker 后须重新验收
- [ ] Docker 重启后数据库数据保留。
- [ ] 强杀 Worker 后验证可重领、耗尽进入 `FAILED`、旧 token 不能提交。
- [ ] 后端/前端 lint、类型、单元和集成测试均通过
- [x] 当前后端 65 项、前端 14 项测试及两端 lint、类型和构建门禁均通过Stage 2 新功能仍需增量验证
### 提交/推送节点
- [ ] `S2-A`FastAPI、持久化、健康端点和错误契约。
- [ ] `S2-B`任务租约、reaper、fencing 和恢复测试。
- [ ] `S2-C`React、Nginx、完整 Compose、CI 和文档。
- [ ] `S2-C`React、Nginx、完整 Compose、CI 和文档;其中 React/Nginx/CI 前置已完成,缺 Worker 与完整 Compose 验收,节点不得提前关闭
- [ ] 每个节点验证后独立提交并推送Stage 2 完成时在本文更新状态。
### 风险门禁
@@ -555,13 +560,13 @@ Stage 0 DONE
## 15. 下一步执行顺序
当前只推进 Stage 1建议按以下顺序开始
当前主线仍是关闭 Stage 1 的真实供应商门禁;已完成的 React/Nginx 工作仅作为 Stage 2 安全前置保留。建议按以下顺序继续
1. 轮换并受控注入新百炼 Key确认真实端点不写入仓库
2. 创建最小 Python 包、数据库初始化、Alembic 和 `db + migrate` Compose
3. fake server 实现并验证三模型适配器契约
4. 运行受控 live smoke记录 provider profile 和能力指纹
5. 用 20 条虚构数据完成写入、检索、重排和幂等闭环
6. 扩展 `make verify`,保存验收证据,完成 S1-A/S1-B/S1-C 提交与推送
1. 在百炼控制台撤销聊天中暴露的旧 Key创建新 Key并仅通过未提交的 Docker Secret 注入
2. 分别运行 `text-embedding-v4``qwen3-rerank``deepseek-v4-flash` 受控 live smoke记录脱敏 provider profile、能力指纹、维度、流式协议和错误映射
3.现有 20 条 synthetic 数据运行真实 Embedding/Rerank/Chat 链路和幂等 seed真实地质资料仍不得出域
4. 关闭 S1-B 并把 Stage 1 标记为 `DONE` 后,再实现 Stage 2 的 Worker 租约、fencing、reaper、恢复测试和完整 Compose
5. Stage 3 实现数字 PDF/DOCX/TXT/Markdown 的隔离解析、页码/章节恢复、审批状态机、切分、向量化写库和可追溯版本激活
6. Stage 4 完成真实向量召回、重排与验证集评测Stage 5 再实现 grounded chat SSE、取消传播、引用解析和聊天 UI不把当前代理分块测试误称为聊天功能完成
在 Stage 1 验收之前,不并行展开真实语料入库、完整 UI 或正式验证集调参
在 Stage 1 验收之前,不展开真实语料云端入库或正式验证集调参;前端可继续做不接触 Secret、不会绕过阶段门禁的离线可用性修正

View File

@@ -21,7 +21,7 @@ bash scripts/init-local-secrets.sh
离线模式使用确定性的 1024 维 feature-hash 向量和词法重排器,只验证数据治理、批处理、迁移、向量写入、权限过滤、检索、重排和幂等性,不把它当成真实模型质量。
```bash
docker compose up -d --build gateway
docker compose up -d --build web
docker compose --profile tools run --rm seed-demo-offline
docker compose --profile tools run --rm seed-demo-offline
```
@@ -46,7 +46,7 @@ Seed 的实际流程是:
### 2.1 查看后端运行效果
内部 API 只连接 `internal` data 网络,只挂载 app 数据库 Secret不连接外网也不挂载百炼 Key。无 Secret、无数据库凭证的 gateway 同时连接 ingressdata 网络,并且仅发布到本机回环地址 `127.0.0.1:8000`。两个容器都以数值非 root 用户运行,根文件系统只读,并移除 Linux capabilities。
内部 API 只连接 `internal` data 网络,只挂载 app 数据库 Secret不连接外网也不挂载百炼 Key。无 Secret、无数据库凭证的 gateway 连接 internal ingress/dataReact/Nginx Web 连接 edge/ingress并且是唯一发布到本机回环地址 `127.0.0.1:8000` 的容器。三个运行容器均使用非 root 用户、只读根文件系统并移除 Linux capabilities。
```bash
curl http://127.0.0.1:8000/health/live

View File

@@ -0,0 +1,91 @@
# React 离线检索演示运行手册
本手册用于启动 React + Nginx + gateway + FastAPI + PostgreSQL/pgvector 的同源离线演示。当前页面只展示合成数据的检索和重排结果,不调用百炼,不生成大模型答案,也不构成地质勘查结论。
## 1. 一键启动
首次运行先创建本地数据库 Secret
```bash
make setup-hooks
bash scripts/init-local-secrets.sh
docker compose up -d --build web
docker compose --profile tools run --rm seed-demo-offline
```
`web` 的依赖链会依次等待 `db -> migrate -> api -> gateway`。离线 seed 完成后刷新页面,即可看到 20/20/20 的演示数据状态。
访问入口:
- React 演示:<http://127.0.0.1:8000/>
- FastAPI Swagger<http://127.0.0.1:8000/docs>
- 数据库 readiness<http://127.0.0.1:8000/health/ready>
- synthetic 状态:<http://127.0.0.1:8000/api/v1/demo/status>
## 2. 推荐演示流程
1. 确认页面顶部持续显示“合成数据、离线模式、不调用云端模型”。
2. 确认切片、向量和可检索计数均为 20。
3. 选择示例问题或输入“花岗斑岩 铜矿化”,设置返回 3 条。
4. 提交检索,查看合成标题、证据摘录、页码、演示排序分和不透明 citation ID。
5. 说明 citation ID 尚不能打开真实报告页,当前没有 LLM 生成答案。
6. 缩窄浏览器到移动宽度,验证表单和来源卡片仍可完整阅读。
## 3. 本地前端开发
先让 Docker Web/API 链保持运行,再启动 Vite
```bash
cd frontend
npm ci
npm run dev
```
Vite 只监听 `127.0.0.1`,并把 `/api``/health``/openapi.json` 代理到本机 Docker Web 入口。前端 API 基址保持同源,构建产物中不存在数据库地址或百炼凭证。
后端 OpenAPI 改变后重新生成类型:
```bash
cd frontend
npm run generate:api
npm run typecheck
```
生成脚本会检查 demo status/search 路径是否存在,然后覆盖 `src/api/schema.generated.ts`;生成结果必须随 API 契约变更一起提交。
## 4. 质量门禁
```bash
make verify
docker compose --env-file .env.example ps -a
```
统一门禁包括:
- Secret、Markdown 链接和 Git diff 检查;
- Ruff、严格 mypy 和 pytest
- Prettier、ESLint、严格 TypeScript、Vitest 和 Vite production build
- Compose 配置与网络/Secret 合同测试。
容器级验收还应确认:
- 只有 `web` 发布 `127.0.0.1:8000`
- `web``gateway` 没有 Secret
- `api` 只连接 internal data 网络,公网 socket 探测失败;
- `gateway` 公网 socket 探测失败Web 因普通 edge 网络可技术性出网,但无 Secret、无数据网络和挂载此残余风险记录于 ADR-0004
- `/api`、健康检查和 Swagger 经 Web 同源代理可用;
- SSE 响应在 gateway 与 Nginx 均不被整体缓冲。
## 5. 停止与清理
停止容器但保留 PostgreSQL 数据:
```bash
docker compose down
```
不要在需要保留验收数据时使用 `docker compose down -v`。该命令会删除 PostgreSQL volume。
## 6. 当前边界
已实现的是可运行的离线检索演示尚未实现登录、真实知识库、文件上传、OCR、生成答案、可点击原文页、会话、反馈、正式评测面板和真实百炼模型探测。只有轮换后的新 Key 完成三模型 smoke 后Stage 1 才能标记为完成Stage 2 也不能因页面可见而提前标记为完成。

View File

@@ -5,7 +5,9 @@
- Stage 0“仓库、安全和设计基线”已完成并推送。
- Stage 1“模型与数据库 PoC”正在进行。
- 当前整体完成度约 12%,合理区间为 10%15%。
- 0003 的设计基线已完成;业务代码完成度约 8%Stage 1 离线数据库/适配器/seed 子闭环已验收,真实百炼 smoke 与前端产品功能尚未完成
- 0003 的设计基线已完成;业务代码完成度约 18%Stage 1 安全离线子阶段约 95%
- 后端离线链路、20 条 synthetic 数据、React 离线演示、Nginx 单入口和四网络隔离均已验收;真实百炼三模型 smoke 仍待旧 Key 撤销并轮换。
- Stage 2 仍为 `TODO`当前只完成了不依赖云密钥的前置Worker 租约、完整 Compose 和产品工作流尚未完成。
- 后续状态、依赖、验收证据、工期和提交节点以 [项目全生命周期 TODO](04-project-todo.md) 为准。
## 文档列表
@@ -18,8 +20,9 @@
| [03-implementation-plan.md](03-implementation-plan.md) | 阶段计划、完成定义、测试与验收清单 | 设计基线完成 |
| [04-project-todo.md](04-project-todo.md) | 从当前状态到最终答辩、发布和归档的任务看板 | Stage 1 进行中 |
| [05-stage1-runbook.md](05-stage1-runbook.md) | Secret 初始化、离线数据库/seed、真实三模型 smoke 和排障命令 | Stage 1 运行手册 |
| [06-frontend-demo-runbook.md](06-frontend-demo-runbook.md) | React/Nginx 离线检索演示、同源 API、开发与验收命令 | 离线前端运行手册 |
| [adr/](adr/) | 不易逆转的架构决策记录 | 持续维护 |
文档中的工作空间地址统一使用 `<workspace-id>` 占位符;真实地址和凭证均属于部署配置,不写入仓库。
建议首次阅读按 00 → 01 → 02 → 03 → 04 的顺序;日常执行查看 04运行 PoC 查看 05。
建议首次阅读按 00 → 01 → 02 → 03 → 04 的顺序;日常执行查看 04数据库/模型 PoC 查看 05React 演示查看 06

View File

@@ -0,0 +1,48 @@
# ADR-0004使用无 Secret 的分层 Web 入口
- **状态:** accepted
- **日期:** 2026-07-12
## 背景
系统需要同时满足三个约束:浏览器只能访问一个本机 Web 端口;数据库感知 API 不能获得公网出口React/Nginx 不能因反向代理而加入数据库所在网络。仅把 API 同时接入普通 `edge` 和内部 `data` 网络会获得公网出口;仅把 API 接入 `internal` 网络时Docker Desktop 又不会可靠发布宿主机端口。
## 决策
采用四层单机拓扑:
```text
browser
-> web (Nginx + React; edge + ingress)
-> gateway (fixed-origin HTTP proxy; ingress + data)
-> api (FastAPI; data only)
-> PostgreSQL/pgvector (data only)
```
- `edge` 是普通 bridge只连接无 Secret 的 `web`,用于宿主机回环端口发布。
- `ingress``internal` 网络,只连接 `web``gateway`
- `data``internal` 网络,只连接 `gateway``api`、迁移和数据库等内部组件。
- 命名的 `egress` 只供显式云模型工具使用;`web``gateway``api` 均不连接该网络。普通 `edge` bridge 仍会给 Web 一个技术上的默认公网出口,不能把网络名称当作防火墙。
- 只有 `web` 发布 `127.0.0.1:8000`gateway、API 和数据库不发布端口。
- `web``gateway` 不挂载数据库或百炼 Secretgateway 固定上游为 `http://api:8000`执行请求体上限、头白名单、SSRF 防护和脱敏 502 映射。
- API 只挂载 app 数据库 Secret以非 root、只读根文件系统运行并通过真实 socket 探测证明无公网出口。
## 取舍
多一层 gateway 会增加一个容器和一次本机网络转发,但当前查询耗时主要来自数据库和模型,额外开销相对可忽略。保留应用层 gateway 可以让固定上游、请求大小和错误脱敏形成可单测契约Nginx 专注静态资源、SPA fallback、安全响应头和 SSE 代理。
在当前 Docker Desktop 上,`edge` 必须保持普通 bridge 才能可靠发布宿主机回环端口;实测 Web 可以从该网络建立公网 TCP 连接。当前接受这一残余风险,因为 Web 无 Secret、无数据网络、无挂载且 Nginx 只代理固定 gateway。需要入口容器零出网的部署必须额外使用主机防火墙或受控出口策略重新验收后再收紧本 ADR。
## 被否决方案
1. **API 直接加入 edge 并发布端口:** 实测仍可访问公网,不满足数据库感知进程无 egress 的要求。
2. **web 直接加入 data 并代理 API** web 会获得到数据库网络的横向连通性,扩大入口层受攻陷后的范围。
3. **只发布 internal 网络中的 API 端口:** 在当前 Docker Desktop 上没有形成可访问的宿主机端口。
4. **只使用 Nginx、删除 gateway** 可以减少一跳,但会丢失已经测试的固定上游、请求头白名单和脱敏错误契约;本阶段优先保持安全边界可验证。
## 验证要求
- Compose 合同测试锁定网络成员、唯一发布端口和 Secret 挂载。
- API 与 gateway 容器公网 TCP 探测必须失败Web 的 edge 出口能力必须被显式记录web/gateway 镜像历史和环境不得出现 Key、数据库密码或内部 DSN。
- 浏览器从 Web 入口访问 SPA、`/api`、健康检查与 SSE 时保持同源。
- 每次改变该拓扑都必须更新本 ADR、部署文档并重新执行容器级验收。

View File

@@ -7,3 +7,4 @@ ADR 用于记录会长期影响系统的技术决策。状态使用 `proposed`
- [0001-use-pgvector.md](0001-use-pgvector.md):选择 PostgreSQL + pgvector 作为第一版向量存储。
- [0002-separate-bailian-protocols.md](0002-separate-bailian-protocols.md):分离百炼 Chat/Embedding 与 Rerank 协议适配器。
- [0003-text-first-scope.md](0003-text-first-scope.md):第一版采用文本优先边界,不宣称地质图空间理解。
- [0004-secretless-web-ingress.md](0004-secretless-web-ingress.md):用无 Secret 的 Nginx Web 与固定上游 gateway 隔离浏览器、API 和数据库网络。