编排台(Console)¶
编排台是 plaita 的可视化控制台:拖拽画布编排流程、schema 驱动的节点配置表单、 Dry-Run 试运行、执行历史与集群管理。后端为 FastAPI,前端随 pip 包分发,安装即用。
安装与启动¶
pip install plaita-console # 含后端 API 与打包好的前端页面
pip install "plaita-console[nodes]" # 连同通用业务节点插件一起安装
python -m plaita_console # 或使用 plaita-console 命令
启动后打开 http://localhost:8080。前置条件只有一个:Redis 可达
(默认 redis://localhost:6379/0,经 PLAITA_CONSOLE_REDIS_URL 覆盖)。
从源码开发则克隆仓库,后端 python run.py、前端 pnpm dev,见仓库内
plaita-console/README.md。
配置¶
全部经 PLAITA_CONSOLE_* 环境变量配置,常用项:
| 变量 | 说明 |
|---|---|
PLAITA_CONSOLE_HOST / PLAITA_CONSOLE_PORT |
监听地址,默认 127.0.0.1:8080;非回环地址启动打 WARNING。官方容器 CMD 固定 --host 0.0.0.0,容器部署用端口映射控制暴露面 |
PLAITA_CONSOLE_REDIS_URL |
Redis 连接 |
PLAITA_CONSOLE_DB_URL |
流程定义持久化(默认本地 SQLite) |
PLAITA_CONSOLE_DB_CONNECT_TIMEOUT / ..._POOL_TIMEOUT |
非 SQLite 库的连接/池超时(默认 5s;黑洞/半开 DB fail-fast) |
PLAITA_CONSOLE_ENGINE_SYNC_DEADLINE |
启动回填墙钟预算(默认 5s;超时仍启动,/health.engine_sync=degraded) |
PLAITA_CONSOLE_ALLOW_INSECURE_ADMIN=true |
本地开发免密;局域网/生产请改设 PLAITA_CONSOLE_ADMIN_API_KEY |
PLAITA_CONSOLE_SECRET_ID / SECRET_KEY |
对外契约接口的 HMAC 密钥 |
仓库根目录的 .env.example
给出 compose 必填项与常用项样例;变量的完整默认值、读取时机、0/非法值语义见
分布式 runbook「环境变量参考」。
企业能力:RBAC / 审计 / 环境晋升¶
RBAC:设置了 PLAITA_CONSOLE_ADMIN_PASSWORD 且 users 表为空时,首次启动
自动创建 admin(平台管理员);未设置时不会自动建号,也不会打印随机密码,
前端会进入「创建管理员」向导。角色三级:
- admin:全部权限(用户管理 / 凭据 / 集群 / 审计 / 生产环境删除)
- editor:编排与执行(增删改流程、发布、试跑、启动执行)
- viewer:只读
多租户部署下,admin 是租户内角色,不等于平台身份(#260 / #261 / #263):
/api/users 的全局账号变更(创建 / 改角色 / 改密 / 删除)、全局服务停止,
以及集群管理面的全部端点只允许 platform_admin(或平台级 API-Key)
执行,拒绝写 *.denied 审计。集群面包括全局生命周期与基础设施配置
(服务启停 / stop-all / process-docker 模式切换 / 基础设施 CRUD 与容器
启停 / 集群登记、切换、删除与配置保存),也包括 GET 读面(实例列表、
基础设施 URL/健康详情、集群列表与完整配置、模板)——这些响应含数据面
连接串与 env 密钥,没有租户内语义(#263)。所有 cluster 响应再经序列化层
脱敏:URL 的密码段与 PASSWORD/SECRET/TOKEN/KEY 等 env 值显示为
***。/api/dispatch/ledger 对非平台身份按本租户过滤(显式传他租户
403 + 审计),平台身份默认全量。租户 admin 只能经
/api/tenants/{tid}/members/* 管理本租户成员,且不能操作平台管理员
账号。任何已认证角色都可以修改自己的密码。租户 admin 的
GET /api/users 只返回本租户成员。
POST /api/dispatch/one 的 shadow / dry_run / tenant_id 是服务端
权威字段(#262):shadow 只取 PLAITA_CONSOLE_DISPATCH_SHADOW(请求体
同名字段忽略);dry_run=false 仅平台身份可写,租户 editor 传 false 会被
强制干跑并留下 WARNING;tenant_id 取认证上下文,只有平台身份可代传其它
租户,跨租户请求 403 + 审计。派发台账同时记录认证主体 actor 与生效租户。
POST /api/dispatch/one 与 /api/dispatch/plan 的容量路由带队列维度
(A64-05 / B75-01):只有注册 metadata queue_name 与本次派发队列一致的
worker 才可能成为目标,跨队列候选以稳定原因 queue: mismatch 进入
rejected;命中目标后消息投到目标 worker 消费的队列。避免 v1/v2 金丝雀
或滚动升级时目标指向另一队列、消息落到无人消费队列后交接耗尽、
执行被误终态化为 AffinityHandoverExhausted。
队列是三态(B75-01):queue_name 非空且非纯空白 = 声明(known);
缺失/空白/非字符串 = unknown,不再按默认队列假设。unknown 只拒绝
定向、不拒绝广播:默认 PLAITA_DISPATCH_UNKNOWN_QUEUE=skip 下
unknown 以 queue: unknown 进入 rejected,消息仍投到调用方队列且不带
target_instance_id,legacy default worker 仍能抢到。需要显式兼容旧部署时
设 PLAITA_DISPATCH_UNKNOWN_QUEUE=assume-default:仅在请求默认队列
plaita:flow:queue 时把 unknown 当默认队列,响应/台账标记
queue_source=assumed-default 并留 WARNING(含 instance_id 列表);其余
情况仍是 unknown。plan / dispatch / 台账 / 指标
plaita_dispatch_unknown_queue_total{queue} 共用同一判据。新 worker 注册时
写 queue_name + queue_contract_version=2。
GET /api/dispatch/ledger(A54-02):平台身份返回全量台账与全局队列对账;
租户身份只返回当前世代的本租户条目(台账写入 tenant_generation,
同名租户重建后旧世代条目不可见;缺失世代的旧行 fail-closed),且不返回
全局队列计数——queue_length=null、error 说明对账仅平台身份可用。
GET /api/services 及其详情/拓扑读面(#265):基础设施与内部组件的合成
条目在响应序列化层脱敏——连接串 URL 的 userinfo 口令与凭据键值换成
***,保留 scheme/host/port/path/db。租户 viewer 仍可看 worker 实例与
非敏感状态,但读不到口令。
登录后走会话 token(7 天);X-Admin-API-Key 服务账号兼容(等价 admin)。
角色/密码变更即时撤销旧会话。「用户」页管理账号与角色。
审计:「审计」页留痕管理面敏感操作(发布、保存、删除、执行、凭据变更、 用户管理),含操作人 / IP / 时间 / 元数据——机密内容永不入审计。
删除租户时 audit_logs 不物理删除(合规证据保留):租户 admin 的
GET /api/audit 只返回当前世代(tenants.created_at 之后)的记录,
平台身份可跨世代全量审计。自定义节点/属性类型/Copilot 会话/部署记录随
租户删除事务清理;日志 Stream(plaita:logs:*)按条目 tenant_id 清理;
任务队列按 console/worker/allowlist/同族版本队列的键集合排空。404
补清扫遇到残留 flow 时返回 409 具名拒绝,create_tenant 也拒绝任何同名
继承(要求显式清理/归档)。
环境晋升:console 实例经 PLAITA_CONSOLE_ENV 标识环境(dev/test/prod):
- 每次发布写部署记录(环境、操作人、定义 SHA-256 指纹)
- 「导出晋升包」→ 目标环境 console「导入」:指纹校验防篡改,导入为草稿 再发布,形成 dev → prod 的受控晋升
PLAITA_CONSOLE_ENV=prod时删除操作需要 admin
本地单机模式(零依赖上手)¶
不装 Redis 也能用:console 启动时若 Redis 不可达,自动进入本地单机模式—— 流程在 console 进程内执行、执行历史与节点级 trace 存 SQLite,首次启动还会 写入 3 个示例流程(快速开始 / 循环与映射 / HTTP 调用)。
本地模式的边界:集群管理、任务队列、事件管理不可用(对应页面明确提示); 审批/事件的挂起-恢复支持单进程语义(checkpoint 存 SQLite,重启后仍可显式 恢复)。定时触发器也只在本地档运行:以集群档启动后,SQLite 里的本地调度 保留但不执行、也不在集群调度列表显示(切回本地档恢复,或按相同 cron 在 集群档重建)。完整对比见下文与 部署模式。
四档部署形态(本地单机 / 单机标准 / 分布式 / SDK 嵌入)的完整对比与升级路径 见 部署模式。
调试:数据固定与单节点重放¶
试跑面板支持 n8n 式调试:
- 节点级 IO 检视:点击时间线节点展开完整输入/输出 JSON(不再截断)
- 数据固定(pin):把某次试跑的节点输出固定下来,后续试跑跳过该节点 真实执行(HTTP 调用等重副作用节点尤其实用),面板以 mock 高亮
- 仅运行此节点:上游取固定值、下游 mock 化无副作用,只真实执行目标节点
固定值只在试跑内存里,不会写进流程定义。
连接器¶
pip install plaita-nodes 自带的连接器节点(凭据按名引用,机密不落流程定义):
| 节点 | 用途 | 凭据数据 |
|---|---|---|
feishu_webhook / wecom_webhook / slack_webhook / dingtalk_webhook |
IM 群通知 | {"url": ...};钉钉可加 "secret" 自动加签 |
generic_webhook |
任意 Webhook POST | {"url": ...} |
api_request |
通用 REST(静态 Header 鉴权) | {"base_url": ..., "headers": {...}} |
sql_query |
SQL 查询/写入(SQLAlchemy) | {"url": "postgresql://..."} 或 host/port/user/password/database |
email_send |
SMTP 邮件 | {"host", "port", "username", "password", "use_tls"/"use_ssl"} |
新增连接器即一个新的节点类 + entry-point 注册,无需改动编排台。
凭据¶
外部服务机密(webhook 地址、数据库密码等)集中在「凭据」页管理:Fernet
加密落库,流程节点按名引用(credential: "feishu-bot"),流程定义里不落
明文。保存后自动导出加密文件供引擎节点运行时解密;console 拉起的
flow_worker 自动带上密钥环境。
密钥来源:PLAITA_CREDENTIALS_KEY 环境变量,或自动生成于 DB 同目录的
.plaita-credentials.key。多机部署请统一注入同一密钥。
安全边界(如实说明,A2R25-08/09):凭据行本身是 Fernet 密文。同一个
DB 里另有三列敏感数据——local_executions.input_json(执行 params)、
context_json(checkpoint/$INPUT)与 tenants.contract_secret_key
(对外 HMAC 契约密钥)——自本轮起落盘即字段级加密(enc:v1: 密文,
AES-GCM;主密钥与凭据加密同源,按用途经 HKDF 派生独立子密钥)。ORM 读取
透明解密,执行页/恢复/契约验签不受影响;裸 SQL、sqlite3、shutil.copy2
备份只能看到密文。存量明文行在 init_engine 启动时回填并 VACUUM
(SQLite),回填失败/密钥不可用会打 WARNING 或 ERROR(绝不静默)。
密钥就是数据的一部分:丢失/轮换主密钥会让历史执行 checkpoint 与既有
租户契约密钥不可恢复(读取 fail-loud,不会退回明文)。备份 DB 时必须
同时保护 .plaita-credentials.key(0600),或把 PLAITA_CREDENTIALS_KEY
放进密钥管理设施统一注入;不要把两者放进同一个备份包外发。
SQLite 文件仍在 init_engine 收紧为 0600(0600 是第二道闸,不替代字段
加密);.plaita-credentials* 与 plaita_console.db 已加入 .gitignore
(与 .dockerignore 同口径)。output_json/nodes_json 仍是明文功能副本,
只在读面脱敏(#166/#222)——DB 文件/备份整体仍按敏感数据处置。PostgreSQL
没有文件权限面,字段加密是主要 at-rest 控制。另:错/缺
PLAITA_CREDENTIALS_KEY 时控制台拒绝保存凭据(409
credentials_key_mismatch),启动对账跳过该租户的导出重写(原文件保留),
不会用新 key 覆盖旧密文造成混合密钥。导出文件在有密文时带
__plaita_meta__ 版本头(key_id/格式版本),引擎读侧忽略该保留键。
导出文件默认与 DB 同目录:未显式设置 PLAITA_CREDENTIALS_FILE 时,
SQLite 部署导出到 DB 同目录的 .plaita-credentials.json。以下情形无法推断
共享目录,按历史语义回退当前工作目录并打一次性 WARNING(A67-04):
未设置 PLAITA_CONSOLE_DB_URL、URL 非 sqlite:///(PG/MySQL 等)、内存
SQLite;相对 SQLite URL(如 sqlite:///./x.db)即使两端字符串相同也会按
各自进程 cwd 解析,同样 WARNING。显式设置
PLAITA_CREDENTIALS_FILE / PLAITA_CREDENTIALS_KEY_FILE 时,绝对路径不触发
路径回退告警;若显式值是相对路径,仍按各进程 cwd 解析,并打一次性
WARNING——「值相同」不等于「同一个文件」。DB 敏感列的字段级加密密钥
(与凭据共用 .plaita-credentials.key)走同一套解析与告警。回退只保证
单进程可用——升级后忘记注入 DB env 的 worker 会继续读旧 cwd 文件,并能
解出已吊销/已轮换的旧值,因此
不能把该 WARNING 当噪音。导出文件是 DB 的派生缓存,按 DB 路径落盘让
同一 DB 的多个 console/worker 副本共享同一份文件;不再各按 cwd 持有
一份互不收敛的副本(否则一个副本删除/轮换凭据,其他副本的引擎仍能解出
旧值)。引擎侧 plaita.credentials 用同一规则解析默认路径与密钥文件。
- 多副本部署:共享同一 SQLite DB 的副本无需额外配置;独立启动的
worker 需能读到同一
PLAITA_CONSOLE_DB_URL的绝对路径才能解析到 DB 同目录(console 拉起的 worker 会自动注入绝对路径)。PostgreSQL 等无法 从 DB URL 推断共享目录的后端,必须给所有 console/worker 显式注入同一 绝对路径的PLAITA_CREDENTIALS_FILE与PLAITA_CREDENTIALS_KEY_FILE(并确保该路径持久化/挂卷)。相对 URL 不满足「同一 URL」:各进程会按 自己的 cwd 打开不同 DB。 - 升级迁移(旧版本 → 当前版本):先看 console/worker 启动日志是否出现
「凭据…默认回退到当前工作目录」WARNING。若有,把旧 cwd 的
.plaita-credentials.json/.plaita-credentials.key迁移到 DB 同目录 (或显式绝对路径)后删除旧副本;只改 DB URL 不够,独立 worker 也必须 拿到同一绝对路径。旧 cwd 文件留在原处不会自动清理,其里已删除/已轮换的 值会持续可被该 cwd 的进程解出。 - 容器部署:DB 同目录若在容器内且未挂卷,重启会丢文件;启动对账会从 DB 重建,但请仍把该目录挂持久卷以避免窗口期。
- 启动对账:console 启动时以 DB 为权威重建缺失的导出文件、刷新陈旧 内容(含进程在「导出成功→提交 DB」间被杀留下的条目),并对没有对应租户 的旁文件记 WARNING。对账写入失败会拒绝启动,不让引擎带着缺失/陈旧 的密钥继续跑。租户删除/同名重建会清掉同 slug 的凭据行与导出文件,新租户 不会继承前代密钥。边界(当前定案,自动收敛不做):
- 孤儿侧车只告警:没有对应租户行的旁文件/凭据行只记
extra_files/orphan_tenantsWARNING,不自动删除或隔离——可能是手工放置的共享密钥, 自动收敛需 owner 裁决(B67-01)。需要清理时用对应的DELETE租户 凭据入口或人工移走文件。 - 对账只在启动时:没有周期对账;SIGKILL 落在「导出成功→提交 DB」间
留下的鬼凭据/落后文件,最长到下一次重启、同租户写或显式
DELETE才 收敛。长期不重启的部署请把「重启 console」写进凭据轮换/泄露处置步骤 (B67-02)。 - 慢盘/滚动重启:对账在 SQLite 写锁内逐租户做文件 I/O;租户数极多或 DB 目录在 NFS/慢盘时,第二个实例可能撞 5s busy timeout 而拒绝启动。 fail-loud 是设计,编排层应串行重启或给足重试/超时(B67-03)。
导出写入是原子的:保存/删除凭据时先写同目录临时文件(flush + fsync
+ 0600),再用 os.replace 原子替换目标文件。磁盘写满、超配额或进程中断
不会把上一份完好的导出文件截断成半截 JSON——旧文件逐字节保留,引擎继续
按旧内容解析。
密钥文件权限:console 首次生成密钥时用 O_EXCL 直接以 0600 创建
(不再先 0644 再 chmod,A67-05);并发首建只有一把 key 胜出,落败进程
等待并复用胜者写入的 key,不重新生成,并在复用前把旧版本可能留下的
0644 胜者文件收紧为 0600。DB 字段加密(secure_fields)使用同一密钥
文件,创建/落败复用/收紧口径与凭据侧一致。已存在的 0644 存量 key 会在启动
对账与每次读取时收紧为 0600 并打 WARNING;只读挂载等无法 chmod 的场景会
持续告警(每个路径一次),请修正属主/挂载权限。密钥是解密所有租户凭据的
唯一材料,.plaita-credentials.key 的备份/外发按凭据泄露处置。
引擎读侧 fail-loud:文件不存在视为「尚未配置凭据」;文件存在但为空、
JSON 损坏或顶层不是对象时,plaita.credentials 抛
CredentialStoreCorruptError(CredentialError 子类),报错指明文件路径
并提示重新保存任意一条凭据以重建导出文件,不再静默降级成「凭据不存在」。
多租户隔离:凭据按租户落库与导出——default 租户沿用历史文件
.plaita-credentials.json,其余租户各写旁文件
.plaita-credentials.{tenant_id}.json。引擎侧 plaita.credentials
按租户上下文路由(worker 消费任务时从消息体 tenant_id set,本地档由
执行线程 set;该上下文由 core/runner 随 context 副本带进节点执行线程,
故节点内解析到的租户与调度线程同源):非 default 租户的 flow 只能解析到
自己旁文件里的凭据,default 文件对它不可见,凭据缺失的报错也只列当前
租户文件内的名字。
主要能力¶
- 流程编排:拖拽画布 + 节点面板;节点配置面板由节点 schema 自动生成表单
(表达式字段带
$INPUT/$NODE变量插入),复杂结构回落 JSON 编辑 - 子流程:map / loop / while / parallel 分支等进入子画布编辑,面包屑返回
- Dry-Run:保存前在控制台进程内试运行并查看逐步输出
- 执行与集群:执行历史、集群页一键拉起 flow_worker / 调度 / 事件服务等实例
(
cluster_config.yaml定义服务模板与实例数) - Copilot:AI 辅助生成流程(
PLAITA_CONSOLE_COPILOT_BRAIN可选大脑)
节点插件¶
节点能力三来源:
- 内置 23 种(start / if / switch / loop / while / map / http / event / parallel 等)——随引擎自带(含经 entry_points 自动注册的 delay / approval / redis_queue / kafka_queue / http_callback 扩展节点)
- 插件包
pip install plaita-nodes——agentrun / hitl / gate 等,经plaita.nodesentry-points 自动注册 - 自定义——参照自定义节点编写后,以同样机制打包分发
控制台「节点」页实时展示当前已注册的全部类型及其 schema。