环境配置与服务鉴权
使用环境配置、Provider 绑定和 consumer token 隔离后端部署环境。
同一个 Artifact 应能够部署到多个环境。所有环境差异都通过 Hosted Service Environment 管理,但必须区分 两类配置:应用配置由部署解析后提供给业务进程;运行治理配置由 Hosted Service、OpenResty 和运行载体消费, 不进入业务代码。完整责任划分见架构与资源边界。
Resource Binding 的通用边界、当前公开能力和申请流程见 基础能力与 Resource Binding。数据库 Profile 和 Object Storage Profile 是当前 明确支持的 Hosted Service Provider 绑定;不要因为某项基础能力在 Runtime 中可见,就假设 Hosted Service 已支持该绑定类型。
环境配置
应用配置用于改变同一 Artifact 在目标 Environment 中的业务运行参数。适合放入应用配置的内容包括:
- 日志级别、功能开关和外部服务地址
- 业务运行参数
- 数据库 Profile 或 Allocation 的绑定引用
- Object Storage Profile 的绑定引用
- 第三方凭据 Provider 的绑定引用
敏感值不要直接写入源码、Artifact 元数据、公开文档或普通日志。配置变更应形成可审计的修订,并在部署前确认解析结果。
实例数量、扩缩容策略、CPU/内存限制、通用请求限流和并发保护属于运行治理配置。它们可以在产品界面上归属
目标 Environment,但必须使用 Hosted Service 定义的类型化字段、独立校验和版本化修订,并受 Slot 当前容量
权益约束;不能把 replicaCount、rateLimit 等自定义普通配置键注入 Rust 进程后由业务代码执行。Rust 进程
不得读取或决定副本数,也不应重复实现平台已经承诺的通用流量治理。
当前是否支持某个治理项以及准确参数,以工作区当前 Runtime 方法定义和 Slot 目录为准。方法定义中尚未提供的 治理能力不能通过普通 Environment 配置绕过;平台能力正式提供并完成运行态验证前,文档也不能宣称该治理已生效。
使用前通过当前 CLI 帮助确认参数:
baijimu hosted-service env --help
baijimu hosted-service config-provider --help
baijimu hosted-service config-binding --helpProvider registry 是动态目录。新增 object-storage Provider 不要求修改 Hosted Service contract、Hosted
Service Component 或 CLI;环境先安装对象存储 Package 并注册 Provider,项目再用 config-binding 保存自己的
profileRef。部署解析出的对象 API 地址和 Token 只进入目标 Environment,底层对象存储 AccessKey 不进入
Hosted Service 配置。
变量名称与来源
环境配置使用一个明确的变量列表。每个名称在环境内只能定义一次,来源可以是固定值、环境内资源绑定的字段、 Environment Secret,或资源绑定的调用凭据。Provider 返回哪些字段,不决定应用获得哪些环境变量。
例如,环境先创建名为 orders 的数据库绑定,再提交如下配置文件:
{
"schemaVersion": "1.0.0",
"variables": [
{ "name": "LOG_LEVEL", "source": { "kind": "literal", "value": "info" } },
{ "name": "DATABASE_URL", "source": { "kind": "resource", "binding": "orders", "field": "databaseUrl" } },
{ "name": "API_KEY", "source": { "kind": "secret", "key": "EXTERNAL_API_KEY" } }
],
"applicationConfig": { "featureEnabled": true }
}baijimu hosted-service env update --workspace-id WORKSPACE_ID \
--environment-id ENVIRONMENT_ID --config-file ./environment.jsonapplicationConfig 是应用自己的不透明配置,作为 HOSTED_SERVICE_CONFIG_JSON 提供给进程;它的字段不会自动
变成环境变量。设置为 null 表示不提供这份应用配置。固定环境变量使用字符串。
资源字段只支持顶层字符串、数字、布尔值;嵌套对象、数组和空值不能隐式展开。同一个字段同时出现在 Provider
的 values 和 env 中会被视为歧义。变量只通过当前环境的绑定名称访问资源,不能填写任意 profileRef。
资源字段、Secret 和绑定凭据都通过 Secret 通道注入。绑定凭据使用
{ "kind": "bindingCredential", "binding": "workflow" } 作为来源,不再自动生成 WORKFLOW_TOKEN。
没有声明的字段和 Secret 不注入;重复变量名、缺少绑定或字段、引用不存在的 Secret 都会报错。
绑定名由使用方定义,在环境内唯一。primary 没有特殊意义,同一个环境可以绑定多个数据库。配置引用中的
绑定名和迁移目标都必须有效;被当前配置或未完成迁移引用的绑定不能直接改名、停用或删除。
Environment Secret
每个 Project Environment 拥有独立的 Secret snapshot。Secret 只在所属 Environment 的配置和部署流程中可用, 不能跨 Environment 读取或复用。新增 Environment 后平台会自动创建对应 snapshot,不需要修改后端 Artifact、 额外登记密钥或调整组件版本。数据库 Profile 凭据仍由 Database Provider 根据绑定在部署时解析,不复制进 Environment Secret。
先读取当前键名和 Environment Revision。该操作只返回元数据,不返回任何 Secret 值:
baijimu hosted-service env secret-get \
--workspace-id WORKSPACE_ID \
--environment-id ENVIRONMENT_ID更新使用显式 patch,并携带读取到的 version:
baijimu hosted-service env secret-update \
--workspace-id WORKSPACE_ID \
--environment-id ENVIRONMENT_ID \
--expected-version ENVIRONMENT_REVISION \
--upserts-json-file ./secrets.json \
--remove-secret-key RETIRED_TOKEN \
--change-reason "rotate provider credential"--upserts-json-file 只写入或替换文件中出现的键,--remove-secret-key 只删除明确指定的键,其他现有键保持
不变。并发期间 Environment Revision 已变化时,服务端拒绝旧版本 patch;重新读取元数据并核对变更后再提交,
不要覆盖未知的新修订。
把历史普通配置迁移为 Secret 时,在同一次命令中传入 --remove-config-key NAME,并在 upsert 文件中提供
同名 Secret。普通配置删除、加密 Secret snapshot 和统一 Environment Revision 在同一个事务中提交;不能
把 --remove-config-key 当作普通配置删除接口。空 patch、隐式清空、写入与删除同一个键、删除不存在的键,
都会失败关闭。--remove-config-key 将同名固定变量原子改为 Secret 来源;Secret 存储键与变量名称属于不同
用途,只有显式声明的来源决定取哪个值,写入 Secret 不会覆盖同名固定变量。
Secret 值不会通过查询、更新响应或管理界面回显。敏感 JSON 应通过权限受控的临时文件或标准输入边界提供, 避免直接写在命令行、Shell 历史、项目 Git、Artifact、普通配置或日志中。更新只改变 Environment 配置源事实; 已有业务进程仍需通过受支持的重新部署或重启流程取得新 snapshot。
读取部署后的公开 Endpoint
Endpoint 的端口是 Runtime Revision 的平台事实,不属于业务配置。业务 Artifact 只声明 Endpoint 的数量、 稳定名称、协议和通用鉴权模式;部署时平台分配端口,并把每个端口拆分为业务进程环境变量。后端不需要解析 JSON 或根据 Project ID、环境 ID、域名格式和监听端口自行推导启动端口。
每个 Endpoint 都注入一个 APP_ENDPOINT_<NORMALIZED_NAME>_PORT,其中名称转换规则为:小写字母转为大写,
连字符 - 转为下划线 _。例如:
APP_ENDPOINT_API_PORT=20000
APP_ENDPOINT_MANAGEMENT_PORT=20001端口只在当前 Runtime Revision 内有效,示例值不固定。业务进程必须在固定的 127.0.0.1 上,使用对应
Endpoint 的命名端口变量启动 listener;不得使用或硬编码 APP_BIND_HOST、APP_PORT、PORT、BIND_ADDR、
listenPort 示例值或 proxyListenPort。平台不会注入这些变量,也不提供 APP_PORT 兼容注入。
Runtime 仍可能提供 RUNTIME_ENDPOINTS_JSON 作为 Endpoint 元数据,供确实需要读取 baseUrl 等公开信息的
适配器使用;它不是业务监听端口契约,业务启动不得依赖它解析端口。
固定健康检查协议
业务进程必须在每个声明的 HTTP Endpoint listener 上提供:
GET /healthz进程完成初始化并能够接收业务流量后返回 2xx;未就绪时返回非 2xx。/healthz 是 Hosted Service 的固定
协议常量,不属于 hosted-service.toml、Artifact Endpoint 元数据或 Environment 配置。平台不会读取或覆盖健康路径,
并拒绝任何健康路径覆盖字段。健康探针不执行 Endpoint consumer token 鉴权,
因此 handler 只能返回就绪状态,不应输出配置、凭据或业务数据。
当多个 Project Environment 共用一个 Slot Runtime 时,每个业务进程仍只会收到属于自身 Deployment 的 Endpoint,
并保留模块定义中的逻辑名称(例如 api)。Slot 父进程使用的命名空间前缀、访问令牌和其他
内部聚合字段不会进入业务进程的 RUNTIME_ENDPOINTS_JSON;若适配器读取该可选元数据,只能依赖下表定义的七个公开字段,
监听端口仍必须从对应的命名环境变量读取。
每个 Endpoint 对象包含以下字段:
| 字段 | 类型 | 含义 |
|---|---|---|
name | string | Endpoint 的稳定名称,例如 api 或 management |
protocol | string | Endpoint 协议;当前为 http |
listenPort | integer | Runtime 为该 Endpoint 动态分配的业务进程监听端口 |
routeHost | string | 平台为该 Endpoint 分配的路由主机名 |
baseUrl | string | 调用方可使用的完整公开基础 URL |
authMode | string | 该 Endpoint 当前采用的 Hosted Service 鉴权模式 |
primary | boolean | 是否为当前服务的主 Endpoint |
示例结构如下;实际值由当前部署动态生成:
[
{
"name": "api",
"protocol": "http",
"listenPort": 20000,
"routeHost": "example.app.baijimu.com.cn",
"baseUrl": "https://example.app.baijimu.com.cn",
"authMode": "none",
"primary": true
},
{
"name": "management",
"protocol": "http",
"listenPort": 20001,
"routeHost": "example-management.app.baijimu.com.cn",
"baseUrl": "https://example-management.app.baijimu.com.cn",
"authMode": "token",
"primary": false
}
]服务需要回传业务 API 地址时,应先按稳定语义选择 name == "api" 的 Endpoint;要求主入口时再同时校验
primary == true,并使用该对象的 baseUrl。缺少匹配项或存在多个匹配项应视为部署契约错误并停止启动,
不要猜测地址、退回旧属性值或从 routeHost 重新拼接 URL。
平台还会注入 RUNTIME_ENVIRONMENT_ID、RUNTIME_DEPLOYMENT_ID、RUNTIME_DEFAULT_USER_ID 和
RUNTIME_DEFAULT_WORKSPACE_ID,用于标识当前业务进程的部署与默认调用上下文。这些值和
RUNTIME_ENDPOINTS_JSON 都是平台拥有的运行时事实:即使环境普通配置、配置绑定或 Secret 中出现同名键,
最终注入值也以当前 Deployment 为准。命名 Endpoint 端口变量同样是平台拥有的运行时事实,业务进程必须为
自己声明的每个 Endpoint 读取对应命名端口,并在 127.0.0.1 上启动 HTTP listener。一个进程声明多个
Endpoint 时应在同一进程内绑定多个 listener,Runtime 仍只启动一个 OpenResty。listenPort 不能替代 baseUrl:
业务监听地址、Runtime 内部代理端口和公网调用地址是三个不同概念,只有 baseUrl 是调用方应保存和使用的稳定地址。
业务、管理与插件生命周期入口
设计后端时,先区分三类调用方,再声明 Endpoint。多租户服务如果需要随 Bundle 安装开通租户、 升级或配置时同步业务配置、卸载时解除绑定,必须提供供插件管理服务调用的生命周期接口。 多租户本身不要求接入插件;不参与这些操作的服务无需声明生命周期入口。
| 入口示例 | 调用方 | 职责与鉴权 |
|---|---|---|
api | 应用、模块、工作流、业务用户 | 执行业务请求,由业务后端识别租户、校验业务权限并隔离数据。 |
management | 服务运营人员、租户管理员 | 管理业务配置、账号或配额;分别校验租户内管理权限和跨租户权限。 |
lifecycle | 百积木插件管理服务 | 执行获授权的 Bundle 生命周期操作,校验平台调用插件的独立凭据和完整安装归属。 |
这三个名称是设计示例,不是平台保留名称,也不会自动生成 handler 或授予权限。 推荐将平台生命周期入口与业务入口、人工管理入口分开声明,便于独立路由和限制接口范围。 生命周期接口也可以与管理接口共用 listener,但必须按路由分别认证、授权,不能让插件凭据获得所有管理能力。 插件可以由业务服务直接实现,也可以由独立适配服务实现;后者只在自己的项目中声明执行入口。
Hosted Service 中声明插件监听入口
下面是同一进程提供三类入口的完整清单示例。它假定业务 API 自行验证租户凭据,人工管理入口有 Hosted Service 入口保护并继续校验管理员权限,生命周期入口自行验证专用插件 Bearer Token:
contract_version = "3.0.0"
[service]
entrypoint = "target/release/example-service"
[[service.endpoints]]
name = "api"
protocol = "http"
auth_mode = "none"
primary = true
required = true
[[service.endpoints]]
name = "management"
protocol = "http"
auth_mode = "token"
required = true
[[service.endpoints]]
name = "lifecycle"
protocol = "http"
auth_mode = "none"
required = true业务进程分别读取 APP_ENDPOINT_API_PORT、APP_ENDPOINT_MANAGEMENT_PORT 和
APP_ENDPOINT_LIFECYCLE_PORT,在 127.0.0.1 启动对应 listener;每个 listener 都实现固定的
GET /healthz。不要硬编码端口,也不要为声明之外的入口猜测端口或回退到业务端口。
这是业务应用自己的监听入口,不是 Hosted Runtime 的运维或热更新管理端口。
lifecycle 示例使用 auth_mode = "none",只表示 Hosted Service 不校验 consumer token。
生命周期 handler 必须先验证 Authorization: Bearer <平台调用插件的独立凭据>,再解析和执行请求;
缺失或错误凭据必须拒绝。该凭据通过受控服务端配置提供,不得复用业务租户 Token、人工管理 Token
或插件调用平台的凭据。不要把这个示例直接改成 token 并复用管理密钥,否则会把两种独立认证合同混在一起。
声明多个端口不会自动建立网络隔离;实际可达范围以部署后的路由和访问策略为准。
从 Endpoint 到插件登记
- 在
lifecyclelistener 实现 JSON POST 路由,例如/plugin/bundle/lifecycle。该路径由插件实现定义, 不是平台自动生成的接口;业务 listener 不应同时挂载这个管理操作路由。 - 按开发插件服务实现当前 SDK、
INSTALL / UPGRADE / CONFIGURE / DETACH、两个资源阶段、幂等重试和执行顺序校验。 插件管理租户业务状态与安装归属,Runtime 拥有 Bundle 安装状态;DETACH不默认授权删除租户数据。 - 发布并部署后,按名称从当前 Environment 的 Endpoint 查询结果选择
lifecycle,取得真实 HTTPSbaseUrl,再与实际实现的 POST 路径组成bundleExecution.executeUrl。不能登记 listener 的本机地址、 动态端口、人工管理页面 URL,也不能误取 primary 业务入口。 - 按插件注册与管理登记稳定
pluginId、协议和资源阶段,设置平台调用插件的凭据;需要反向解析引用时,再配置插件调用平台的独立凭据。 地址属于插件目录,不写入 Bundle;凭据不写入源码、清单或普通业务配置。 - 回读登记与凭据状态,确认执行地址、协议和阶段;然后绑定 Bundle 并在目标 Runtime 验证完整生命周期。 验证缺失或错误凭据被拒绝、业务租户凭据不能调用生命周期接口、跨安装归属不能操作其他租户、 同一操作重试不会重复开通租户、解绑后迟到的旧操作不能恢复绑定。
AI 交付时应报告实际 Endpoint 名称、执行 URL、插件登记身份、凭据配置状态和生命周期验证结果,
不输出凭据内容。只有端口监听成功或 /healthz 通过,不能证明插件已接入。
Endpoint 鉴权
Hosted Service 支持两类边界:
authMode=token:由环境级 consumer 和绑定到当前 Endpoint 的 token 控制;Hosted Service 不解释 consumer 或 Endpoint 的业务用途。authMode=none:Hosted Service 不校验平台服务 token,业务应用必须自己实现完整鉴权。
Endpoint 鉴权只支持 none 和 token。Endpoint 名称仅用于稳定路由,不会把 management、primary
或其他名称转换成不同鉴权语义。
选择 none 不代表 Endpoint 自动安全,也不代表可以信任前端传入的用户身份。
管理 consumer 和 token 前先读取当前命令:
baijimu hosted-service consumer --help
baijimu hosted-service token --help
baijimu hosted-service auth-config --helptoken list 同时返回数字 id 和公开 tokenId。撤销时必须传 tokenId(例如 hsvc_...),数字 id
只是平台内部记录编号,不是撤销契约:
baijimu hosted-service token list WORKSPACE_ID --environment-id ENVIRONMENT_ID
baijimu hosted-service token revoke WORKSPACE_ID \
--environment-id ENVIRONMENT_ID \
--token-id TOKEN_ID删除 consumer 会在同一事务中撤销它的全部 token,并软删除 consumer;该操作必须显式确认:
baijimu hosted-service consumer delete WORKSPACE_ID \
--consumer-id CONSUMER_ID \
--yes撤销不存在的 tokenId、删除不存在的 consumer,或操作不属于当前工作区的资源都会失败关闭,不会返回伪成功。
token 创建后只在安全位置保存。不要把它:
- 写入平台应用
accessUrl - 提交到项目 Git
- 返回给浏览器端长期保存
- 输出到构建或部署日志
Hosted Service Endpoint token 只保护别人访问当前服务,不授权当前服务反向读取 Bundle 资源。后端需要查询 自己的 Bundle 安装账本、查看或执行工作流与定时任务时,应另外申请 Bundle Capability,并将其 Token 存入当前 Environment Secret。两类 Token 的签发主体、scope 和生命周期不同,不能互换。
接收 Runtime 调用上下文
后端作为模块方法的 HTTP 目标时,Runtime 不会自动向 Rust 服务注入 SDK 对象或完整上下文 JSON。模块必须
在 methodBody 中显式选择稳定上下文字段并映射到自己的 Header、Path、Query 或 Body;Rust 服务再在
HTTP 入口把映射结果转换成类型化请求上下文,并对必需字段失败关闭。
准确字段、出现条件、position: "context" 映射,以及可直接使用的 Rust/Axum 提取示例见
模块调用上下文。自定义上下文
Header 不是 Endpoint 凭据;服务鉴权和业务用户授权仍需分别校验。
业务用户身份
服务 token 只回答“哪个调用方可以访问这个 Endpoint”,不自动回答“当前业务用户是谁、能操作哪些数据”。
平台应用调用后端应用时,应由受控网关传递已验证的工作区和用户上下文;后端应用不能把普通请求参数中的 userId 当成可信身份。管理员代办能力必须单独定义权限和审计。
后端同时作为模块方法目标,并按逻辑引用解析出的地址和凭据调用其他 Runtime 服务时,使用 Runtime 用户委托传递当前已验证用户:
- 入站请求存在
X-Runtime-Actor-Assertion时,只在当前请求内保存和转发; - 出站调用同时携带目标引用的 Bearer token 和该 assertion;
- 后端不签名、不解析、不记录 assertion,跨服务的下一跳轮换由 Runtime 完成;
- assertion 只对引用解析返回的
url有效,不能作为 Hosted Service 普通公开 Endpoint 的鉴权方式。