Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ IM / HTTP -> Channel Adapter -> Gateway -> Queue/Outbox -> Agent Worker
- PostgreSQL 控制面、migration、显式 `init` 初始化和受认证的 Admin API;
- Tenant/App/Revision 的草稿、发布、回滚、灰度候选和乐观锁;
- OpenAI 模型 provider,以及不访问外部服务的 deterministic fake provider;
- InMemory 与 PostgreSQL runtime storageSession/Event、Reply Outbox 和租约恢复;
- InMemory、PostgreSQLtenant-scoped Redis runtime storage;Redis 当前覆盖 Session/Event、Memory、Reply Outbox 和租约恢复;
- 普通及流式 HTTP Chat API,企业微信自建应用文本 webhook,Telegram 文本 long polling;
- OpenTelemetry trace/metrics、Prometheus 导出路径、审计事件和脱敏错误;
- Docker Compose 本地验证、Kubernetes Kustomize base,以及版本 tag 触发的 GHCR 镜像发布。
Expand Down
20 changes: 20 additions & 0 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ services:
retries: 12
start_period: 10s

redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s

service:
build:
context: ..
Expand All @@ -25,6 +37,8 @@ services:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
TRPC_POSTGRES_DSN: "${TRPC_POSTGRES_DSN:-postgres://${POSTGRES_USER:-trpc}:${POSTGRES_PASSWORD:-trpc-local-password}@postgres:5432/${POSTGRES_DB:-trpc_agent}?sslmode=disable}"
TRPC_API_TOKEN: ${TRPC_API_TOKEN:-local-api-token}
Expand All @@ -34,6 +48,11 @@ services:
TRPC_ADMIN_TENANTS: ${TRPC_ADMIN_TENANTS:-*}
TRPC_MODEL_API_KEY: ${TRPC_MODEL_API_KEY:-local-development-key}
TRPC_SESSION_BACKEND: ${TRPC_SESSION_BACKEND:-postgres}
TRPC_REDIS_ADDR: ${TRPC_REDIS_ADDR:-redis:6379}
TRPC_REDIS_PASSWORD: ${TRPC_REDIS_PASSWORD:-}
TRPC_REDIS_DB: ${TRPC_REDIS_DB:-0}
TRPC_REDIS_KEY_PREFIX: ${TRPC_REDIS_KEY_PREFIX:-trpc:runtime:v1}
TRPC_REDIS_SECRET_REF: ${TRPC_REDIS_SECRET_REF:-env/trpc-redis-password}
TRPC_MODEL_PROVIDER: ${TRPC_MODEL_PROVIDER:-openai}
TRPC_MODEL_NAMES: ${TRPC_MODEL_NAMES:-gpt-4o-mini}
TRPC_MODEL_ENDPOINT_HOSTS: ${TRPC_MODEL_ENDPOINT_HOSTS:-api.openai.com}
Expand All @@ -54,3 +73,4 @@ services:

volumes:
postgres-data:
redis-data:
12 changes: 12 additions & 0 deletions deploy/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ TRPC_ADMIN_TOKEN=local-admin-token
TRPC_ADMIN_TENANTS=*
TRPC_MODEL_API_KEY=local-development-key
TRPC_SESSION_BACKEND=postgres
# Redis is available in Compose for an explicit TRPC_SESSION_BACKEND=redis
# deployment. The local service has no password; external deployments should
# inject TRPC_REDIS_PASSWORD through a secret manager.
# TRPC_REDIS_ADDR=redis:6379
# TRPC_REDIS_PASSWORD=
# TRPC_REDIS_DB=0
# TRPC_REDIS_KEY_PREFIX=trpc:runtime:v1
# TRPC_REDIS_SECRET_REF=env/trpc-redis-password
# TRPC_REDIS_DIAL_TIMEOUT=500ms
# TRPC_REDIS_READ_TIMEOUT=500ms
# TRPC_REDIS_WRITE_TIMEOUT=500ms
# TRPC_REDIS_POOL_SIZE=10
TRPC_MODEL_PROVIDER=openai
TRPC_MODEL_NAMES=gpt-4o-mini
TRPC_MODEL_ENDPOINT_HOSTS=api.openai.com
Expand Down
12 changes: 11 additions & 1 deletion docs/docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ demo 仅支持 PostgreSQL、本地开发使用,遇到部分或多租户歧义
- 容器健康检查使用静态 `/app/trpc-healthcheck`,不会依赖 distroless 镜像中的 shell。
- `/healthz` 用于存活和 startup probe;`/readyz` 用于流量接入前的 readiness。
- `TRPC_SERVICE_IMAGE` 可在 CI 或本地覆盖服务镜像名;默认值是 `trpc-agent-service:local`。
- Compose 同时启动 Redis 7(AOF、`redis:6379`)作为可选运行时后端;只有显式设置
`TRPC_SESSION_BACKEND=redis` 时服务才会读写它。生产环境应替换为受管 Redis,并通过 Secret
Manager 注入 `TRPC_REDIS_PASSWORD`。

填充后的 `deploy/service.env` 已被 `.dockerignore` 排除,不会进入 Docker build context;该文件
仍可能被 Compose 读取,因此不要提交到 Git,也不要把它作为生产 Secret 管理方案。
Expand Down Expand Up @@ -185,7 +188,14 @@ curl --fail http://127.0.0.1:8080/readyz
| `TRPC_MODEL_NAMES` | 否,`gpt-4o-mini` | 逗号分隔模型白名单 |
| `TRPC_MODEL_ENDPOINT_HOSTS` | 否,`api.openai.com` | 逗号分隔 HTTPS endpoint host 白名单 |
| `TRPC_MODEL_SECRET_REF` | 否,`env/trpc-model-api-key` | 运行时 Secret 引用,不是 Secret 值 |
| `TRPC_SESSION_BACKEND` | 必需,显式 `postgres`/`inmemory` | Compose/Kubernetes 示例使用 `postgres`;MySQL 控制面当前应使用 `inmemory` |
| `TRPC_SESSION_BACKEND` | 必需,显式 `postgres`/`redis`/`inmemory` | Compose/Kubernetes 示例使用 `postgres`;Redis 模式只提供 Session/Memory,MySQL 控制面当前应使用 `inmemory` |
| `TRPC_REDIS_ADDR` | Redis 模式必需 | Redis `host:port`;Compose 默认使用 `redis:6379` |
| `TRPC_REDIS_PASSWORD` | 否 | Redis 认证密码,使用 Secret Manager 注入,不进入日志或快照 |
| `TRPC_REDIS_SECRET_REF` | 否,`env/trpc-redis-password` | Redis Backend Profile 的可选 SecretRef |
| `TRPC_REDIS_DB` | 否,`0` | Redis logical database |
| `TRPC_REDIS_KEY_PREFIX` | 否,`trpc:runtime:v1` | tenant-scoped key 前缀 |
| `TRPC_REDIS_DIAL_TIMEOUT` / `TRPC_REDIS_READ_TIMEOUT` / `TRPC_REDIS_WRITE_TIMEOUT` | 否 | Go duration,限制 Redis 客户端 I/O |
| `TRPC_REDIS_POOL_SIZE` | 否 | 大于 `0` 时覆盖连接池大小 |
| `TRPC_DEMO_MODE` | 否,`false` | 仅由 `quickstart.sh --demo` 显式启用;要求 `TRPC_MODEL_PROVIDER=fake`,不读取模型凭据 |

模型 API key 只在受信任的 Secret Resolver/Factory 路径中使用,不进入 Execution Plan、缓存、
Expand Down
26 changes: 19 additions & 7 deletions docs/docs/ops.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 运维、可观测性与生产风险

> 本页把 [生产架构设计](architecture.md) 转成可执行的发布、监控、恢复和风险检查表。
> 当前仓库只有控制面领域模型、快照和最小 Runner spine;Gateway、队列、真实 IM/Storage
> Adapter、Dashboard 和告警规则仍是后续平台实现,不应把本页当作已经部署的运行手册。
> 本页按代码和自动化测试证据标注能力状态,不等同于一份已经完成生产部署的运行手册。
> 控制面、Runner spine、SQL/InMemory RuntimeStore,以及可选的 Redis Session/Memory
> provider 已落地;真实 IM 验签、Dashboard、生产告警平台和其他外部存储适配仍需单独验收。

## 运行边界与值班目标

Expand Down Expand Up @@ -209,8 +210,19 @@ backpressure。高峰保护使用租户级 token bucket、全局队列上限、

## 当前实现状态与后续门禁

本仓库目前可以验证 Tenant、Agent App/Revision、Model Profile、Backend Profile、无密钥
Execution Plan、Runner policy 和 Tenant-scoped Session 的模型/边界测试;不能验证真实 IM
验签、跨节点 CAS、队列至少一次投递、SQL/Redis 迁移或生产告警。后续实现每落地一个 Adapter
都必须补充:双租户隔离测试、重复/乱序/验签失败测试、provider 一致性契约测试、故障注入、
审计字段检查和 `mkdocs build --strict`。
状态只代表当前仓库已有的实现和测试证据:

| 能力 | 状态 | 证据或边界 |
| --- | --- | --- |
| Tenant、Agent App/Revision、Model Profile、Backend Profile、Execution Plan 和 Runner policy | 已实现 | 控制面模型、快照和策略测试 |
| InMemory RuntimeStore | 已实现 | Tenant-scoped Session/Event/Memory 契约测试 |
| PostgreSQL RuntimeStore | 部分实现 | 迁移、CAS、幂等、Outbox 和可选 live conformance;需要外部 DSN 才能验证重启恢复 |
| Redis RuntimeStore | 已实现(Issue #108) | `TRPC_SESSION_BACKEND=redis`,Redis Session/Memory、WATCH/MULTI CAS、租户隔离和 readiness PING;可选 live reconnect 测试 |
| Redis capability 范围 | 明确限制 | 仅 `session`、`memory`;`summary`、`knowledge`、`artifact`、`audit` 和独立向量库 provider 会被拒绝 |
| Redis/PostgreSQL 迁移、双写、shadow read、自动 cutover | 未实现 | 迁移方案仍需后续工具和演练,不能把切换当作 Redis provider 自带能力 |
| 对象存储(S3/OSS)和生产向量库(Qdrant/Milvus/pgvector) | 未实现 | 当前只有接口/能力边界,未提供真实外部 adapter 或检索闭环 |
| 真实 IM 验签、多媒体、Dashboard 和生产告警平台 | 未实现或部分实现 | WeCom/Telegram 的已交付范围以各自 adapter 文档为准;本页不宣称生产运营集成 |

后续每落地一个 Adapter 或运维组件,都必须补充:双租户隔离测试、重复/乱序/验签失败测试、
provider 一致性契约测试、故障注入、审计字段检查和 `mkdocs build --strict`。只有在提供外部
依赖并实际运行 live suite 后,才能把相应的 `✅*` 证据升级为生产验收结论。
64 changes: 54 additions & 10 deletions docs/docs/runtime-storage.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Tenant 运行时持久化契约(Issue #48)

> 本页是 Issue #48 的先行设计与实现 ledger。它把 Session、入站事件和回复
> Outbox 的租户边界、顺序和错误契约固定下来,再由后续代码阶段逐项落地。
> 在 ledger 全部完成前,PR 使用 `Updates #48`,不会把未实现的能力描述成已交付。
> 本页记录 Issue #48 的通用 RuntimeStore 契约,以及 Issue #108 的 Redis 实现边界。
> 代码、测试和部署示例只把已经验证的能力标为已实现;未覆盖的外部后端仍属于后续工作。

## 目标与非目标

运行时持久化的事实源是 PostgreSQL。每个操作都必须显式带 `tenant_id`;
PostgreSQL 仍是控制面和默认运行时事实源;Redis 是可选的共享运行时后端。每个操作都必须显式带 `tenant_id`;
Session/Runner 使用的命名空间只用于防碰撞,不能替代数据库授权。第一阶段覆盖:

- Session 元数据、状态版本和生命周期;
- `message_event` 入站幂等事实、事件序号和执行状态;
- `reply_outbox` 分段回复、租约/fencing、重试和供应商回执。

Issue 不实现 Redis、Memory/Knowledge/Artifact 生产适配、AuditEvent/usage/cost、
Issue #48 不实现 Memory/Knowledge/Artifact 的其他生产适配、AuditEvent/usage/cost、
完整 IM webhook/media、分布式调度、KMS/Vault 或告警平台。API principal 继续由
Gateway HTTP 层的进程内幂等存储保护;跨进程 durable inbound claim 只在已验证
Channel principal 上启用,因为 `message_event.binding_id` 必须引用真实的控制面 Binding。
Expand Down Expand Up @@ -137,9 +136,53 @@ provider reconciliation,`accepted` 直接确认,`rejected` 重试,`unknown
## Bootstrap 与恢复

Bootstrap 必须显式选择 Session capability。`TRPC_SESSION_BACKEND=postgres` 时,
必须同时提供已迁移的 `TRPC_POSTGRES_DSN`;未知值、缺失 DSN 或 migration 验证失败
均 fail-closed。`inmemory` 只用于开发和测试,并在 readiness/启动日志中明确显示
非持久化。新进程连接同一 DSN 后应能读取已有 Session、事件和未发送 Outbox。
必须同时提供已迁移的 `TRPC_POSTGRES_DSN`;`TRPC_SESSION_BACKEND=redis` 时,必须提供
`TRPC_REDIS_ADDR` 并在启动和 readiness 阶段成功 PING。未知值、缺失地址、连接失败或
migration 验证失败均 fail-closed,不会静默回退到 InMemory。`inmemory` 只用于开发和测试,
并在 readiness/启动日志中明确显示非持久化。新进程连接同一后端后应能读取已有 Session、
事件、Memory 和未发送 Outbox。

### Redis 实现范围(Issue #108)

Redis provider 通过 Backend Profile 的 `Provider: "redis"` 选择,只注册 `session` 和
`memory` capability;`summary`、`knowledge`、`artifact`、`audit` 等 capability 在 Catalog
校验阶段拒绝 Redis。每条 profile binding 必须使用 `redis://` endpoint;若
设置 `SecretRef`,它只能解析到当前 tenant 的 Redis 密码,密码不会进入 profile、快照、日志
或错误文本。Provider 构造时再次校验 endpoint/secret scope,避免不同 tenant 复用错误配置。

每个 tenant 使用一个 Redis key:

```text
<TRPC_REDIS_KEY_PREFIX>:<hex(tenant_id)>
```

默认前缀为 `trpc:runtime:v1`。key 的 tenant 部分使用 UTF-8 字节 hex 编码,避免简单拼接造成
边界碰撞。value 是版本化 JSON 状态文档,当前 `version` 为 `1`,包含 Session、Event、
event history、Reply Outbox、correlation、Memory 和 index handoff 集合。写入使用
`WATCH/MULTI` CAS;event 序号、重复消息 claim、lease/fencing 和完整 reply batch 在一次原子
状态更新中提交。

Redis key 没有隐式 TTL。Session、事件、历史、Memory 和 Outbox 不会因为连接池或重启自动过期;
保留、归档和删除必须由显式业务操作或后续运维工具完成。当前没有 Redis/PostgreSQL 迁移、
双写、shadow read 或自动 cutover 工具;迁移方案仍按 Backend Profile 版本切换另行设计。

主要环境变量如下:

| 变量 | 必需/默认 | 说明 |
| --- | --- | --- |
| `TRPC_REDIS_ADDR` | Redis 模式必需 | `host:port`,地址不写入错误或日志 |
| `TRPC_REDIS_PASSWORD` | 否 | 通过 Secret 注入的 Redis 密码;不写入配置快照 |
| `TRPC_REDIS_SECRET_REF` | 否,`env/trpc-redis-password` | Backend Profile 可使用的租户 SecretRef |
| `TRPC_REDIS_DB` | 否,`0` | Redis logical database,范围 `0..32768` |
| `TRPC_REDIS_KEY_PREFIX` | 否,`trpc:runtime:v1` | 共享实例的命名空间前缀 |
| `TRPC_REDIS_DIAL_TIMEOUT` | 否 | Go duration,例如 `500ms` |
| `TRPC_REDIS_READ_TIMEOUT` | 否 | Go duration,例如 `500ms` |
| `TRPC_REDIS_WRITE_TIMEOUT` | 否 | Go duration,例如 `500ms` |
| `TRPC_REDIS_POOL_SIZE` | 否 | 大于 `0` 时覆盖客户端连接池大小 |

本地 Compose 已包含带 AOF 的 Redis 7 服务;生产/Kubernetes 仍应使用外部 Redis,并通过 Secret
Manager 注入密码。可选 live conformance/reconnect 测试读取 `REDIS_RUNTIME_TEST_ADDR`;未设置
时显式 skip,不把本地 miniredis 测试冒充生产 Redis 证据。
真实验收测试使用可选的 `POSTGRES_RUNTIME_TEST_DSN`,并要求该 DSN 已有可写的
`POSTGRES_RUNTIME_TEST_TENANT_ID` 与 `POSTGRES_RUNTIME_TEST_BINDING_ID`;测试会执行
完整 RuntimeStore 操作、关闭连接、重新打开连接并验证 Session/Event/History/Outbox
Expand All @@ -156,12 +199,13 @@ Bootstrap 必须显式选择 Session capability。`TRPC_SESSION_BACKEND=postgres
| Bootstrap 显式 Session capability 与 fail-closed | 3 | 环境配置、RuntimeStore-backed session.Service、重启恢复测试 | ✅ |
| durable Event payload/history 与完整 Event 状态生命周期 | 4 | `runtime_event_history`、fresh delegate replay、状态迁移测试 | ✅ |
| Outbox worker/reconciliation/provider delivery | 5 | fenced worker、重试/死信/过期 lease 与 provider 测试 | ✅ |
| Redis RuntimeStore/MemoryStore 与 tenant-scoped bootstrap | Issue #108 | `runtime/storage/redis` miniredis conformance、配置/Catalog 边界、Compose 服务与可选 live reconnect 测试 | ✅* |
| 真实 PostgreSQL/InMemory conformance 与 fresh-process restart | 6 | `POSTGRES_RUNTIME_TEST_DSN` 可选 live suite 与 reopen 证据 | ✅* |
| verified Channel duplicate Runner suppression | 6 | RuntimeStore claim + 并发 Gateway Runner invocation-count 测试 | ✅ |
| 租户越权、取消、脱敏和防御性返回 | 1–6 | 双租户 conformance 与错误边界测试 | ✅ |
| `go test`、race、vet、build、MkDocs strict | 最终 | PR 验证记录与 CI | ✅ |

`✅*` 表示测试代码和重启路径已交付;live PostgreSQL 证据只有在 CI/本地实际
提供上述 DSN 时才可勾选,未设置 DSN 的默认测试运行会 skip。
`✅*` 表示测试代码和重启路径已交付;live PostgreSQL/Redis 证据只有在 CI/本地实际
提供对应 DSN/地址时才可勾选,未设置变量的默认测试运行会显式 skip。

在代码阶段完成后,本表必须与 PR 描述同步;未完成项目保留为明确的后续阶段。
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ require trpc.group/trpc-go/trpc-agent-go v1.11.2

require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/alicebob/miniredis/v2 v2.34.0
github.com/go-sql-driver/mysql v1.8.1
github.com/go-telegram/bot v1.23.0
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.6.0
github.com/redis/go-redis/v9 v9.6.1
go.opentelemetry.io/otel v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
Expand All @@ -22,21 +24,20 @@ require (

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/creack/pty v1.1.24 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/openai/openai-go v1.12.0 // indirect
github.com/panjf2000/ants/v2 v2.10.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
Expand Down
Loading
Loading