在普通前端应用里,Renderer 常被理解为“把数据变成节点”的一层。但当界面描述来自智能体时,它同时承担了翻译、验证和授权。它决定哪些意图可以进入应用,也决定哪些操作可以离开应用。
先翻译,再渲染
智能体输出的组件名并不等于真实组件。Renderer 需要先在注册表里找到宿主认可的实现,再根据 Schema 校验属性。未知组件不是“尽量猜一下”,而应该稳定降级;不合法属性也不能悄悄穿透到运行时。
模型负责提出 UI 意图,宿主负责解释意图;解释权就是控制权。
Action 比组件更敏感
一个组件渲染失败,通常只影响当前表面;一个未经验证的 Action 却可能跨越网络、改变数据甚至触发外部系统。因此 Action 不应该只按名称查找,而需要同时校验能力范围、任务上下文与参数大小。
UI intent
→ component registry
→ props schema
→ local interaction
→ action allowlist
→ host transport
好的边界也带来更好的体验
安全边界不是只为阻止错误。它也让产品行为更可预测:未知动作可以明确禁用,离线状态可以保留本地输入,重复提交可以被任务标识拦截。用户得到的不是更多限制,而是一套不会因为模型偶然输出而失去一致性的交互。
当 Renderer 被看作信任边界,组件设计、协议设计与产品体验就不再是三个分离的话题。它们共同回答同一个问题:系统愿意把多大的解释权交给智能体。
分层校验,不把前端当成最终授权者
Renderer 的授权范围是客户端允许哪些能力被调用;业务接口是否允许当前用户执行操作,仍由服务端决定。一个合理的顺序是先验证结构,再验证引用与资源约束,最后在动作执行处校验身份和业务权限。
| 边界 | 示例检查 | 失败处理 |
|---|---|---|
| 消息结构 | 版本、属性类型、消息大小 | 拒绝当前更新并记录原因 |
| 组件图 | 未知组件、重复 ID、循环引用、深度 | 隔离有问题的表面 |
| 内容与资源 | 链接协议、图片来源、富文本 | 安全占位或明确提示 |
| 客户端动作 | 动作名、参数范围、任务有效期 | 禁用或拒绝发送 |
| 服务端操作 | 会话、资源归属、幂等约束 | 返回明确错误,不修改数据 |
限制名称并不足够:一个允许的 openLink 动作仍可能收到危险 URL;一个允许的删除动作仍可能携带别人的资源 ID。需要同时校验参数和当前上下文。
一个容易误判的重复提交
用户点击确认后网络变慢,界面重新连接并重试。即使按钮已经禁用,服务端仍可能收到两次请求。宿主应给一次业务意图分配稳定的请求标识,重试复用该标识,服务端在当前用户和操作范围内处理幂等。
仅在浏览器内记一个“已提交”布尔值挡不住重放;仅用任务 ID 去重也可能错误阻止同一任务中的后续合法操作。标识的粒度需要对应业务意图,而不是页面或整个会话。
把拒绝路径也当成产品功能
未知组件显示可理解的降级提示,网络中断保留草稿,权限错误引导重新登录或返回可访问内容。日志记录版本、Surface 标识和错误类型即可定位多数问题,避免把完整输入、会话令牌和私人数据写进去。
验证时至少覆盖:合法请求成功、未知动作被拒绝、跨用户资源被拒绝、重复请求不重复写入。最后一种需要用真实服务端行为证明,不能只看按钮是否置灰。
继续阅读:A2UI 运行时、前端工程化的验证边界。参考 OWASP Authorization Cheat Sheet。