Skip to content

fix: harden CI and complete a2a-sdk V2 lifecycle migration - #538

Merged
liujuanjuan1984 merged 16 commits into
mainfrom
fix/ci-lint-gate-strictness
Oct 24, 2026
Merged

liujuanjuan1984 merged 16 commits into
mainfrom
fix/ci-lint-gate-strictness

Conversation

@liujuanjuan1984

@liujuanjuan1984 liujuanjuan1984 commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

目的

本 PR 一次性收口 CI/静态检查问题、依赖升级、最新审计结果,以及 SDK 升级引入的完整 V2 任务生命周期迁移。旧 LegacyRequestHandler、私有 guard store 和私有 REST streaming 路径已实际删除,不再通过新增 issue 延后处理。SDK 与锁定依赖的权威版本以 pyproject.toml 和 uv.lock 为准。

主要变更

SDK V2 生命周期与持久化

  • 运行时与 migrations extra 使用同一权威 pin,避免 extras 与运行时解析出不同版本。
  • OpencodeRequestHandler 直接继承 SDK DefaultRequestHandlerV2。
  • 数据库后端改用 SDK VersionedDatabaseTaskStore 与 DatabaseTaskEventStream,通过 version CAS 检测并发写入并提供事件重放。
  • 内存后端使用 CopyingTaskStoreAdapter;需要版本接口时统一通过 SDK LegacyTaskStoreAdapter 转换。
  • 删除 GuardedTaskStore、DatabaseTaskStoreCompat、私有 atomic SQL upsert 和 first-terminal-state-wins 策略。
  • output modes 改由公开 RequestContextBuilder 扩展点记录并持久化到 Task metadata;终态后释放进程内缓存。
  • 追加式 stream artifact 在公共 store wrapper 中压成单个累计文本或最新 tool-call snapshot,防止 SDK 的 Part 追加语义改变原有持久化契约。
  • 直连 Message、early failure、follow-up contextvars、取消、订阅和关闭均运行在 SDK V2 生命周期内,并保留 memory/database 双后端回归。

REST、JSON-RPC 与安全边界

  • 删除 server/rest_tasks.py,REST 核心路由直接使用 SDK RestDispatcher 公共方法。
  • 删除对 RestDispatcher._handle_streaming、_setup_active_task 和 _versioned_store 的依赖。
  • stream budget 改为通过公开 RequestHandler.on_message_send_stream/on_subscribe_to_task 包装,protobuf event 按 A2A wire JSON 计量。
  • message payload shape guard 统一放在中间件,同时覆盖 HTTP+JSON 与 JSON-RPC:拒绝跨 transport envelope、legacy content、小写 role 和 kind/type/file Part 形状。
  • core REST 成功响应采用 application/a2a+json;AIP-193 错误响应继续使用 application/json。
  • Agent Card protobuf ParseError 映射到协议错误,而不是误报为 peer unavailable。

依赖与审计

  • 吸收 deps: bump the uv-all-updates group across 1 directory with 5 updates #541 的依赖升级,并在现有兼容范围内刷新 lock,消除 wheel 安装解析与 CI 锁定组合分叉。
  • 修复本轮新披露的 certifi、filelock、opentelemetry-api、virtualenv 风险。
  • 运行时与开发依赖 pip-audit 均无已知漏洞;刷新后 uv pip list --outdated 为空。

审查中发现并已解决

  • 仅替换 store 而保留 legacy handler 会破坏 get/save 接口,已同时迁移 handler 与 store。
  • output-mode 记录最初仍依赖 SDK 私有 _setup_active_task,已改为公开 RequestContextBuilder。
  • SDK V2 会按多个 Part 保存追加 artifact,直接移除旧 aggregator 会改变持久化结果;已补公共 wrapper 与端到端回归。
  • 文档仍宣称保留 legacy/compat/private streaming,已同步改为实际 V2/versioned 语义。
  • 测试仍直接调用 SDK 私有 _handle_streaming,已删除并改为公共路由黑盒测试。

验证

  • bash ./scripts/doctor.sh:通过;827 passed;总覆盖率 92.65%;critical-file coverage policy、Ruff、mypy、pre-commit、wheel/sdist 与安装后 CLI smoke 全部通过。
  • bash ./scripts/conformance.sh:8/8 passed。
  • pip-audit:No known vulnerabilities found。
  • uv pip list --outdated:刷新后无输出。

兼容性与影响

  • application/a2a+json 是 A2A 成功响应的专用 JSON 媒体类型,不是新的响应体封装;body 仍是 A2A ProtoJSON 对象,错误仍是 AIP-193 JSON。客户端、反向代理和网关需要允许 +json,只精确匹配 application/json 的消费者需要更新。
  • SDK 请求解析器会忽略未知字段以支持向前兼容;本服务仍通过中间件显式拒绝已知的 legacy/跨 transport 形状,避免 unknown-field 容忍掩盖协议混用。
  • 终态任务 follow-up 使用 SDK UnsupportedOperationError;已取消任务的重复取消仍保持幂等返回。
  • 数据库模式会创建/使用 task_versions 与 task_events。既有数据库需先执行 README/升级指南中的 a2a-db upgrade。
  • 支持范围仍是单进程 SQLite;不得由多个 worker、容器或副本共享同一 SQLite 文件。

关联

@liujuanjuan1984 liujuanjuan1984 changed the title fix(ci): reject pre-commit rewrites in the lint gate (#537) fix(ci): harden the lint gate and enable Ruff security rules (#537, #539) Oct 2, 2026
Bumps the uv-all-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [fastapi](https://github.com/fastapi/fastapi) | `0.141.1` | `0.143.0` |
| [pydantic](https://github.com/pydantic/pydantic) | `2.13.5` | `2.14.1` |
| [mypy](https://github.com/python/mypy) | `2.3.1` | `2.4.0` |
| [pre-commit](https://github.com/pre-commit/pre-commit) | `4.6.2` | `4.7.0` |
| [ruff](https://github.com/astral-sh/ruff) | `0.16.9` | `0.17.0` |



Updates `fastapi` from 0.141.1 to 0.143.0
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.141.1...0.143.0)

Updates `pydantic` from 2.13.5 to 2.14.1
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](pydantic/pydantic@v2.13.5...v2.14.1)

Updates `mypy` from 2.3.1 to 2.4.0
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](python/mypy@v2.3.1...v2.4.0)

Updates `pre-commit` from 4.6.2 to 4.7.0
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](pre-commit/pre-commit@v4.6.2...v4.7.0)

Updates `ruff` from 0.16.9 to 0.17.0
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.16.9...0.17.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.143.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: uv-all-updates
- dependency-name: pydantic
  dependency-version: 2.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: uv-all-updates
- dependency-name: mypy
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: uv-all-updates
- dependency-name: pre-commit
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: uv-all-updates
- dependency-name: ruff
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: uv-all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
@liujuanjuan1984 liujuanjuan1984 changed the title fix(ci): harden the lint gate and enable Ruff security rules (#537, #539) fix(ci): harden lint gate and absorb toolchain dependency updates (#537, #539, #542) Oct 17, 2026
@liujuanjuan1984
liujuanjuan1984 marked this pull request as ready for review October 17, 2026 07:08
@liujuanjuan1984

Copy link
Copy Markdown
Collaborator Author

独立审查结论

审查范围:dc8e629...ed7f5cc,共 24 个变更文件、8 个提交。

结论:未发现剩余阻塞问题,建议 CI 通过后合并。 改动整体围绕 issue 需求展开,没有明显冗余或偏离;SDK 升级的 wire-level 变化已显式同步到代码、测试和文档。

已发现并收敛的问题

  1. lint 指纹测试原先只做文本断言:无法证明 repo 指纹检测逻辑真的能识别工作区改写。已新增 behavior test,覆盖 staged tracked change、untracked 新增和 untracked 内容变化。
  2. PR 描述曾声称验证了 SDK keyset 分页,但原测试没有触发 SDK 的 next_page_token:已新增 memory/database 双后端测试,并验证首页任务删除后 token 仍可继续分页。
  3. 其余审查项未发现需要修改的代码问题。

逐项评估

Issue / PR 关系

残余风险

  • application/a2a+json 是客户端可见变化,已同步文档和 OpenAPI;需要客户端接受该媒体类型。
  • 未知字段改为忽略是 SDK 1.2 的前向兼容行为;本项目仍显式拒绝既有 legacy payload 形状。
  • 多副本 cluster store 未启用,不属于本 PR 的部署承诺。

最终验证

  • doctor.sh:通过,916 tests,覆盖率 93.48%
  • 运行时 / 开发依赖 pip-audit:无已知漏洞
  • conformance:8/8
  • CI:Default Toolchain、Protobuf 6、Python 3.11/3.12/3.13 全部通过(run 40052808432)

@liujuanjuan1984 liujuanjuan1984 changed the title fix(ci): harden lint gate and absorb toolchain dependency updates (#537, #539, #542) fix: harden lint gate and upgrade a2a-sdk to 1.2.3 (#537, #539, #542) Oct 24, 2026
@liujuanjuan1984

Copy link
Copy Markdown
Collaborator Author

补充审查:媒体类型与 SDK 私有封装治理

1. application/a2a+json 的含义

它不是另一套响应体结构,也不是本仓私有格式。payload 仍是 JSON/ProtoJSON,只是用 A2A 专用媒体类型标明这份 JSON 遵循 A2A HTTP+JSON binding 语义。类似 application/problem+json、application/vnd.api+json 的 +json structured suffix。

影响主要有三点:

  • 客户端、代理或网关如果硬编码只接受 application/json,需要允许 application/a2a+json。
  • 日志、缓存、压缩和内容协商逻辑应按 +json 后缀识别为 JSON。
  • 成功响应和错误响应的媒体类型不再相同:成功为 application/a2a+json,错误仍是 AIP-193 application/json。

本 PR 已同步 OpenAPI、运行时响应和文档。该行为来自 a2a-sdk 1.2.x,不是本仓自行发明的响应模式。

2. SDK 私有封装审计

已移除:

  • BudgetedRestDispatcher 不再继承 SDK RestDispatcher 并覆盖私有 _handle_streaming。
  • REST 流预算改为包装公开 RequestHandler 的两个流方法,并通过 protobuf-to-stream-JSON 计量保持原有 SSE 前限流语义。

保留但已明确记录的私有边界:

  • 自定义 REST 流路由仍调用 RestDispatcher._handle_streaming:SDK 公开 REST 路由会重新执行自身版本校验并把缺省版本识别为 0.3,与本仓中间件协商的 1.0 冲突;因此保留局部、可测试的 SDK SSE framing hook。
  • ManagedLegacyRequestHandler:仍依赖 LegacyRequestHandler 的私有生命周期 hooks,迁移由 [Enhancement] 补齐 SDK 生命周期与升级兼容性,并跟踪 legacy 队列替换 #533 独立跟踪,不能在当前 PR 中安全替换。
  • DatabaseTaskStoreCompat:需要默认 DatabaseTaskStore 未公开的 atomic first-terminal-state-wins 语义和 legacy row conversion;有 shape drift fail-fast 和迁移回归。
  • JSON-RPC dispatcher overrides:用于注册 opencode.* 扩展、错误脱敏和自定义成功/失败 envelope;核心 method schema 与 response helper 继续复用 SDK。

这些边界已写入 docs/compatibility.md 的 “Intentional SDK Private Seams”,后续每次 SDK 升级都必须重跑 shape、handler lifecycle、transport media-type 和 conformance 回归。

3. 结论

未发现剩余阻塞问题。可安全移除的 SDK dispatcher 私有继承已拆掉;剩余私有边界都有明确协议原因、回归覆盖或独立 issue 跟踪,不再作为隐性技术债。

最终验证:

  • doctor.sh:917 tests,覆盖率 93.47%
  • conformance:8/8
  • CI:Default Toolchain、Protobuf 6、Python 3.11/3.12/3.13 全部通过(run 40053880352)

@liujuanjuan1984

Copy link
Copy Markdown
Collaborator Author

与 codex-a2a PR #382 的对比审查

对比结论

codex-a2a PR #382 在本轮 SDK 升级中最大的优势是:直接采用 SDK 1.2.x 的 VersionedTaskStore 生态接口,而不继续维护私有 unversioned store。opencode-a2a 本 PR 已完成可安全拆除的 dispatcher 私有继承,但尚未完成该 handler/store 架构迁移。这一点不把 #533 当成解决方案,明确保留为实际工程差距。

维度 codex-a2a #382 opencode-a2a #538 评价
数据库任务存储 VersionedDatabaseTaskStore + DatabaseTaskEventStream 仍为 GuardedTaskStore / DatabaseTaskStoreCompat codex-a2a 更符合 SDK 生态,值得作为本仓下一步迁移参考
内存任务存储 CopyingTaskStoreAdapter,版本接口用 SDK LegacyTaskStoreAdapter 仍为私有 GuardedTaskStore(InMemoryTaskStore) codex-a2a 更简洁
Request handler SDK DefaultRequestHandlerV2 SDK LegacyRequestHandler + 私有生命周期 hooks codex-a2a 领先;本仓迁移会影响输出协商、取消、订阅、断连和关闭语义
REST 流预算 保留 BudgetedRestDispatcher 私有子类 已改为公开 RequestHandler 包装,但自定义流路由仍局部使用 SDK _handle_streaming 本仓在 dispatcher 继承上更收敛;两者都还没有完全避开 SDK 私有 SSE hook
严格 payload 形状 集中在 HTTP middleware,同时保护 REST 和 JSON-RPC 分散在 REST parser 和 JSON-RPC core path,已有双传输测试 行为等价;codex-a2a 的集中式 guard 更容易避免后续漂移
终态 follow-up 有意保留 InvalidParamsError 服务合同 跟随 SDK 的 UnsupportedOperationError 兼容性取舍不同:codex-a2a 更保守,本仓更贴近 SDK 默认语义
Agent Card ParseError 映射为 A2AClientProtocolError 已移植为 A2APeerProtocolError / invalid_agent_card 本 PR 已吸收
direct Message 生命周期 有 streaming/非 streaming 回归 已移植等价回归 本 PR 已吸收
follow-up contextvars 有 correlation context 回归 已用本仓 trace ContextVar 增加等价回归 本 PR 已吸收
SDK 版本文档 README、compatibility、conformance 同步 README 和 compatibility 已同步 基本对齐

本仓值得 codex-a2a 参考的做法

  1. StreamBudgetedRequestHandler 通过公开 RequestHandler 包装流预算,避免继承 RestDispatcher 并覆盖私有 _handle_streaming。
  2. json_event_size 对 protobuf 事件先转为 A2A stream JSON 再计算预算,使公开 handler 包装和 wire-level 预算语义一致。
  3. 自定义 REST 成功响应用统一 A2AJSONResponse,同时锁定 OpenAPI response media type。
  4. 增加 SDK keyset ListTasks 首页任务删除后 token 仍有效的双后端回归。
  5. 用 docs/compatibility.md 的 “Intentional SDK Private Seams” 长期记录每个私有 hook 的原因和回归边界,而不是只写在 PR 讨论里。

本 PR 仍需诚实保留的差距

  • 未迁移到 DefaultRequestHandlerV2。
  • 未使用 VersionedDatabaseTaskStore 和 DatabaseTaskEventStream。
  • 未删除 GuardedTaskStore、DatabaseTaskStoreCompat 和 first-terminal-wins 私有策略。
  • 自定义 REST 流路由仍使用 SDK RestDispatcher._handle_streaming。

因此,若目标是完全遵循 SDK 生态,下一步应是实际完成 handler/store 迁移,而不是继续增加 issue 数量或把 #533 当作已经解决。

最终验证

  • 924 tests,覆盖率 93.49%
  • conformance 8/8
  • CI 五项全部通过:run 40054905396

@liujuanjuan1984 liujuanjuan1984 changed the title fix: harden lint gate and upgrade a2a-sdk to 1.2.3 (#537, #539, #542) fix: harden CI and complete a2a-sdk 1.2.3 V2 lifecycle migration Oct 24, 2026
@liujuanjuan1984

Copy link
Copy Markdown
Collaborator Author

独立审查结论

本轮已完成 SDK V2 生命周期迁移的代码、契约、文档和依赖复核。当前没有剩余的阻塞问题,也未把已知问题转成后续 issue:

  • handler 已实际继承 SDK DefaultRequestHandlerV2,数据库使用 VersionedDatabaseTaskStore + DatabaseTaskEventStream,内存使用 SDK copying/legacy adapter。
  • LegacyRequestHandler、GuardedTaskStore、DatabaseTaskStoreCompat、EventQueueLegacy、私有 REST _handle_streaming、_setup_active_task 覆写和 _versioned_store 读取均已从运行时代码移除。
  • payload shape guard 已集中在中间件,并同时以 HTTP+JSON、JSON-RPC 黑盒测试覆盖。
  • PR 与 [Enhancement] 补齐 SDK 生命周期与升级兼容性,并跟踪 legacy 队列替换 #533 的遗留项已全部实现,因此关联关系已从 Relates to 修正为 Closes #533。

发现、风险与收敛

  1. handler/store 必须原子迁移:只替换 VersionedDatabaseTaskStore 而保留 legacy handler 会因 get/save 接口和生命周期不同而破坏任务执行。现已迁移整条 V2 active-task 生命周期,并用 memory/database 双后端覆盖 early failure、follow-up、取消、订阅、直连 Message 和关闭。
  2. output-mode 持久化仍依赖 SDK 私有 hook:已将 _setup_active_task 覆写替换为公开 RequestContextBuilder,终态后释放进程内缓存,持久值仍从 Task metadata 恢复。
  3. 移除旧 aggregator 后出现真实回归:SDK 会把 append artifact 保存为多个 Part,而非旧实现的单个累计 Part。现由公共 store wrapper 统一压缩 text/reasoning,tool-call 保留最新 snapshot,并修正非 streaming SendMessage 返回值;memory/database 均有回归。
  4. 私有 SDK 测试耦合残留:删除直接调用 RestDispatcher._handle_streaming 的测试,改为公开路由黑盒验证。
  5. 文档与实现分叉:兼容性文档、架构文档和升级指南已删除 first-terminal-wins、legacy/compat、私有流路由等失实描述,改为 V2 CAS/versioned/event-stream 语义。
  6. 安装解析漂移:wheel 的宽松兼容范围原本会把新安装解析到比 lock 更新的 FastAPI/SQLAlchemy 等版本。已在现有约束内刷新 lock,并以同一 doctor 再验证,避免 CI 组合与用户安装组合分叉。

application/a2a+json 如何理解

这是 A2A 1.0 / SDK 1.2 为 HTTP+JSON 成功响应规定的专用媒体类型,不是 A2A 自定义了另一层响应体:body 仍是任务、消息或列表的 ProtoJSON 结构。错误响应继续使用 AIP-193 application/json。影响主要在 Content-Type 消费侧:客户端、代理和网关要允许 application/a2a+json(通常也应把 +json 当作 JSON 处理),精确断言 application/json 的代码需要调整。成功/错误媒体类型已同时由运行时和 OpenAPI 测试锁定。

与 codex-a2a PR #382 的对比

本 PR 已借鉴对方经过验证的做法:

  • V2 DefaultRequestHandler、VersionedDatabaseTaskStore、DatabaseTaskEventStream、CopyingTaskStoreAdapter、LegacyTaskStoreAdapter。
  • direct Message 释放、follow-up contextvars、Agent Card ParseError 映射、REST/JSON-RPC 集中 shape guard、application/a2a+json 与公开 RequestHandler streaming wrapper。
  • 公开 SDK 接口的迁移边界说明和 memory/database 双后端生命周期回归。

本 PR 中值得 codex-a2a 反向参考的做法:

  • 不用覆写 SDK _setup_active_task:通过公开 RequestContextBuilder 记录 output modes,SDK 升级面更小。
  • 在公共 store wrapper 中补 stream artifact 紧凑化,保留旧 aggregator 的实际持久化契约。
  • shape guard 不只覆盖 message.content,还把 lowercase role、kind/type/file Part 形状以及跨 transport envelope 一并前置拒绝。
  • 直接锁定并验证轮询式 RPC +json 媒体类型、stream budget 的 protobuf wire 计量、keyset 首页 token 回归和 middleware 双传输测试。

有意保留的差异:

  • 本 PR 跟随 SDK V2,将终态 follow-up 暴露为 UnsupportedOperationError;codex-a2a 为自身兼容合同仍选择 InvalidParamsError。
  • 本 PR 支持单进程 SQLite,不宣称多副本共享数据库;event stream 采用 SDK 原生持久化接口,但不扩大部署承诺。

最终验证

  • bash ./scripts/doctor.sh:通过,827 passed,覆盖率 92.65%,Ruff/mypy/pre-commit/构建与 CLI smoke 全部通过。
  • bash ./scripts/conformance.sh:8/8 passed。
  • pip-audit:No known vulnerabilities found。
  • uv pip list --outdated:无输出。

@liujuanjuan1984

Copy link
Copy Markdown
Collaborator Author

最终 CI 已全部通过(commit d7cee59):Default Toolchain、Python 3.11/3.12/3.13 Runtime Matrix、Protobuf 6 Compatibility 共 5/5。本地 doctor.sh 827 passed、覆盖率 92.65%,conformance 8/8,运行时与开发依赖审计均无已知漏洞。

@liujuanjuan1984 liujuanjuan1984 changed the title fix: harden CI and complete a2a-sdk 1.2.3 V2 lifecycle migration fix: harden CI and complete a2a-sdk V2 lifecycle migration Oct 24, 2026
@liujuanjuan1984
liujuanjuan1984 merged commit 5967792 into main Oct 24, 2026
5 checks passed
@liujuanjuan1984
liujuanjuan1984 deleted the fix/ci-lint-gate-strictness branch October 24, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant