EMQX A2A Registry 命名空间格式化修复解析:HTTP API 中全局命名空间如何以 `null` 呈现
后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载A2AAgent-to-AgentRegistry 是 EMQX 中用于注册、发现和检索 AI Agent 卡片的命名空间化存储能力。本文围绕变更记录 fix-17936.en.md 展开修复了 HTTP API 中属于全局命名空间的 A2A 卡片在返回时namespace字段被格式化为字符串global的问题现在统一以 JSON 的null呈现从而与具体命名空间明确区分。读完本文你将理解该修复的成因、card_out/1格式化函数的实现原理、相关 HTTP API 与 CLI 的完整行为以及测试用例如何固化这一契约。一、变更背景A2A Registry 与命名空间模型A2A Registry 是 EMQX 中面向 AI Agent 场景的注册表功能用于让 Agent 通过标准化的Agent Card代理卡片发布自身能力供其他 Agent 或上层编排系统发现和调用。在 emqx_a2a_registry 应用中可以找到其完整实现emqx_a2a_registry.erl — 核心存储与查询逻辑基于 EMQX 内置的 Retainer保留消息实现卡片的持久化emqx_a2a_registry_api.erl — 基于minirest_api的 HTTP 接口层emqx_a2a_registry_adapter.erl — 卡片输出格式化与注册错误归一化emqx_a2a_registry_cli.erl —emqx_ctl命令行入口。A2A 卡片通过**发现主题discovery topic**存储。从 emqx_a2a_registry_internal.hrl 可知-define(A2A_TOPIC_NS, $a2a). -define(A2A_TOPIC_V1, v1). -define(A2A_TOPIC_DISCOVERY, discovery).emqx_a2a_registry.erl 中discovery_topic/4展示了命名空间对主题的两种构造方式%% 全局命名空间$a2a/v1/discovery/{org_id}/{unit_id}/{agent_id} discovery_topic(?global_ns, OrgId, UnitId, AgentId) - emqx_topic:join([?A2A_TOPIC_NS, ?A2A_TOPIC_V1, ?A2A_TOPIC_DISCOVERY, OrgId, UnitId, AgentId]); %% 特定命名空间{namespace}/$a2a/v1/discovery/{org_id}/{unit_id}/{agent_id} discovery_topic(Namespace, OrgId, UnitId, AgentId) when is_binary(Namespace) - emqx_topic:join([Namespace, ?A2A_TOPIC_NS, ?A2A_TOPIC_V1, ?A2A_TOPIC_DISCOVERY, OrgId, UnitId, AgentId]).也就是说全局命名空间是一个特殊的系统级命名空间它不参与主题前缀而任何具体命名空间都会作为主题前缀出现。在 EMQX 的配置体系中全局命名空间常量定义在 emqx_config.hrl-define(global_ns, global).即global_ns就是原子global——这正是问题产生的地基。二、问题描述字符串global带来的歧义变更记录原文指出Fixed the formatting of A2A cards belonging to the global namespace in the HTTP API. Previously, they would show as the stringglobal. Now, they are formatted asnullto distinguish them from specific namespaces.修复了 HTTP API 中属于全局命名空间的 A2A 卡片格式化问题。此前它们会显示为字符串global现在格式化为null以便与具体命名空间区分。为什么字符串global是有问题的从 emqx.erl 的注释可以印证global原子作为?global_ns使用时会产生歧义produce ambiguous。具体来说JSON 语义歧义客户端无法从namespace: global判断这究竟表示全局命名空间这一特殊标记还是表示一个恰好名为global的具体命名空间数据建模问题全局命名空间并不是一个真实存在的命名空间名称将其序列化为普通字符串会污染数据的语义边界过滤与匹配歧义调用方若基于namespace字段做筛选或去重字符串global与真实命名空间global无法区分。因此修复的目标是全局命名空间的卡片在 API 输出中namespace字段必须为null而具体命名空间仍输出其名称字符串。三、修复实现card_out/1格式化函数的源码级解析修复的核心落在 emqx_a2a_registry_adapter.erl 的card_out/1card_out(Card) - emqx_utils_maps:update_if_present( namespace, fun (?global_ns) - null; (Ns) - Ns end, Card ).其逻辑非常清晰仅当卡片 map 中存在namespace字段时update_if_present才进行转换若该字段的值等于?global_ns即原子global则替换为null其他任何值具体命名空间的二进制字符串原样保留。这里null是 Erlang 的原子在 JSON 编码时会被序列化为 JSON 的null而非字符串。这正是与global字符串在 JSON 层面的本质区别。3.1 调用链API 响应如何走到card_outcard_out/1在 HTTP API 的每个读取路径上都被调用见 emqx_a2a_registry_api.erl列表接口handle_list_cards/2第 269-277 行?OK(lists:map(fun card_out/1, Cards))对每张卡片逐一格式化单卡查询handle_get_card/2第 279-289 行命中后?OK(card_out(Card))。API 输出字段定义在fields(card_out)第 150-159 行包括namespace、id、name、version、description、statusonline/offline和raw原始卡片 JSON。其中status由 emqx_a2a_registry.erl 的lookup_agent_status/1根据客户端在线状态动态计算。3.2 CLI 同样受益修复并不仅限于 HTTP API。emqx_ctl命令a2a_registry list/get同样通过emqx_a2a_registry_adapter:card_out/1输出结果见 emqx_a2a_registry_cli.erl 与 第 109 行。因此通过 CLI 查询全局命名空间卡片时namespace也会呈现为null保证两种管理入口的语义一致。四、行为验证测试用例如何固化契约该修复的行为被 emqx_a2a_registry_api_SUITE.erl 的 CRUD 冒烟测试显式断言。在非命名空间即全局场景下?assertMatch({200, #{namespace : _}}, get_card(?ORG_ID, ?UNIT_ID, ?AGENT_ID, TCConfig)), maybe false ? get_config(namespaced, TCConfig, false), ?assertMatch({200, [#{namespace : null}]}, list_cards(#{}, TCConfig)), ?assertMatch( {200, #{namespace : null}}, get_card(?ORG_ID, ?UNIT_ID, ?AGENT_ID, TCConfig) ) end,两个关键断言list_cards(#{})不带命名空间时等价于全局命名空间返回的列表元素中namespace : nullget_card(...)单卡查询返回namespace : null。同时测试矩阵t_crud/1覆盖了?no_namespace与?namespaced两种配置第 193-194 行确保命名空间化场景下namespace仍能正确输出具体值第 206 行仅断言namespace : _存在。这组用例从正反两个方向锁定了全局为null、具体命名空间为字符串的行为契约。五、实际使用HTTP API 与命名空间解析5.1 接口总览API 由 emqx_a2a_registry_api.erl 的paths/0声明命名空间前缀为a2a方法路径说明GET/a2a/cards/list列出卡片支持org_id、unit_id、agent_id、ns、only_global查询参数GET/a2a/cards/card/:org_id/:unit_id/:agent_id查询单张卡片POST/a2a/cards/card/:org_id/:unit_id/:agent_id注册/更新卡片请求体为{card: {...}}DELETE/a2a/cards/card/:org_id/:unit_id/:agent_id删除卡片其中:org_id、:unit_id、:agent_id三个路径段均须匹配段 ID 正则^[A-Za-z0-9._-]$见 emqx_a2a_registry_cli.erl类型定义见 emqx_a2a_registry_types.erl。5.2ns查询参数与全局过滤命名空间通过ns查询参数传入。在/a2a/cards/list的处理逻辑第 243-250 行中/a2a/cards/list(get, #{query_string : QueryParams} Req) - Namespace0 get_namespace(Req), Namespace case maps:get(only_global, QueryParams, false) of false when Namespace0 ?global_ns - all; _ - Namespace0 end, handle_list_cards(Namespace, QueryParams).值得注意的细节请求方未指定命名空间Namespace0 ?global_ns且未设置only_globaltrue时Namespace被置为all此时 emqx_a2a_registry.erl 会同时用全局与通配命名空间两条主题过滤匹配即默认列出全部命名空间的卡片请求方显式设置only_globaltrue时仅列出全局命名空间的卡片——这类卡片的namespace字段在响应中即为null传入具体ns时仅匹配该命名空间下的卡片其namespace字段输出为对应字符串。5.3 命名空间权限校验与审计在 filter/2 中每个请求都会先经resolve_namespace/2解析目标命名空间若请求方自身处于具体命名空间ActorNamespace / ?global_ns却试图用ns参数操作其他命名空间则返回 403not_authorized第 426-442 行若是受管命名空间还需通过namespace.resource_pre_create钩子确认其存在第 454-465 行否则返回 400 Managed namespace not found。修复还联动优化了审计日志log_ns/1第 447-452 行在请求全局命名空间卡片时向minirest_handler的日志元数据写入namespace global弥补了仅凭 URL 无法区分全局卡片与命名空间卡片的审计盲区代码注释中明确关联了同类问题 emqx/emqx#18653。5.4 响应示例修复后全局命名空间卡片的典型响应如下namespace为null{ namespace: null, id: my.org:my.unit:my.agent, name: some_agent, version: 1, description: description, status: online, raw: {...} }而具体命名空间如my-ns下卡片的响应则为namespace: my-ns。六、前置条件与配置A2A Registry 依赖 EMQX 的 Retainer保留消息功能因为卡片正是以保留消息形式存储在$a2a/v1/discovery/...主题上。若 Retainer 未启用所有接口会返回 404 并附带提示信息见 emqx_a2a_registry_api.erlA2A registry requires the retainer feature. Enable retainer under Retained Messages in the Dashboard, or setretainer.enable truein the configuration.相关开关配置路径由 emqx_a2a_registry_config.erl 定义is_enabled() - emqx_config:get([a2a_registry, enable], false). is_schema_validation_enabled() - emqx_config:get([a2a_registry, validate_schema], true).a2a_registry.enable功能总开关默认false。关闭时 HTTP 接口统一返回 503 Not enabled见 filter/2CLI 命令同样拒绝执行见 emqx_a2a_registry_cli.erla2a_registry.validate_schema注册卡片时是否按 agent_card_schema.json 做 schema 校验默认true。卡片注册时的错误信息也由 emqx_a2a_registry_adapter.erl 统一格式化例如Bad org_id id: xxx、Card does not conform to schema、Namespace not found: xxx等分别对应 400 或 500 响应。七、总结本次修复对应 fix-17936.en.md从数据语义层面消除了 A2A Registry HTTP API 的一个歧义点行为变化全局命名空间卡片的namespace字段由字符串global改为 JSONnull具体命名空间仍输出字符串实现落点card_out/1emqx_a2a_registry_adapter.erl作为唯一格式化出口同时作用于 HTTP API 与 CLI 查询路径保证语义一致契约固化emqx_a2a_registry_api_SUITE.erl 以断言形式锁定了全局返回null的行为防止回归。对于基于 EMQX 构建 Agent 生态的开发者而言理解这一约定至关重要在消费/a2a/cards/list或/a2a/cards/card/:org_id/:unit_id/:agent_id的响应时应以namespace null判定全局命名空间卡片而非字符串global从而避免与真实命名为global的命名空间混淆。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX A2A Registry HTTP API 命名空间隔离机制深度解析EMQX A2A Registry HTTP API 命名空间隔离机制深度解析 A2AAgent to AgentRegistry 是 EMQX 提供的智能后端物联网消息队列通信TypeSpec 全局命名空间场景解析typespec/http-client-js 如何为无顶层命名空间的规范生成 TypeScript 客户端TypeSpec 全局命名空间场景解析typespec/http client js 如何为无顶层命名空间的规范生成 TypeScript 客户端 本文基于编程语言编译器后端Slate v2 API Helper 命名空间重命名以 *Api 后缀消除 DOM 全局变量遮蔽Slate v2 API Helper 命名空间重命名以 Api 后缀消除 DOM 全局变量遮蔽 导读 本文基于仓库中的 Slate v2 Api Helpe前端富文本UI组件上一篇Sendwithus邮件模板安全性分析Apache 2.0许可证下的最佳实践下一篇终极指南如何使用GoSumemory打造专业级osu!游戏直播效果 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考