Skip to content

[P3][Proposal] 控制面统一签名 JSON 传输,移除 typed SDK 路线选择 #150

Open

Description

@jo-hnny

问题

控制面 controlplane.SDK.Call 对同一个 Action 有两条传输路线:typed SDK(调用 *ags.Client 方法)和契约校验后的签名 JSON(dynamic)。选哪条路线由两份手写清单(internal/controlplane/compatibility.go 的 sdkShapes、internal/controlplane/sdk.go 的分发 switch)加运行时契约兼容检查共同决定。此外,internal/cli/cloud_calls.go 绕过 controlplane,直接调用 typed SDK,构成第三条路线。

问题出在“选路线”这件事本身:

  1. 路线会随 SDK 版本、契约变化和 stable/preview channel 静默切换,而两条路线的可观察输出并不相同。
  2. 两份清单加第三条路线需要同步维护,容易漂移。

已确认的现状

基线为当前 main(5bd4a49,AGS/common SDK v1.3.188)。以下结论来自运行时 catalog 与 *ags.Client 方法的反射核对:

Channel 契约 Action 实际走 typed 已登记但被判为 dynamic
stable 54 19 DescribeSandboxInstanceList、DescribeSandboxToolList、StartSandboxInstance
preview 62 18 上述 3 个 + CreateSandboxTool
  • 被判为 dynamic 的原因:契约中 TimeoutSeconds / DefaultTimeoutSeconds 为 int,SDK 为 *uint64;preview 的 CreateSandboxTool.StorageMounts[].Volume 在 SDK 中缺失。
  • 因此 sdk.go 中 StartSandboxInstance 的 typed 分支及 typed token 缓存,在两个 channel 的生产路径上都不可达。创建实例后的 token 缓存实际只由 dynamic 路径的 cacheResourceToken 执行。
  • 同一个 Action CreateSandboxTool,stable 走 typed,preview 走 dynamic。
  • SDK 中另有 31 个与当前契约兼容的 Action 未登记,一直走 dynamic(Registry、Session、Event 等);feat(instance): add sandbox snapshots and source cloning #148 引入的 3 个 Snapshot Action 同理。目前没有观察到这些 Action 因此出错。

两条路线差异造成的可观察影响(代码证据):

  • 返回类型不同:typed 返回 *ags.XxxResponseParams,dynamic 返回 map[string]any。instance pause 的文本输出(pauseResultText)只识别 typed 类型;一旦 PauseSandboxInstance 切到 dynamic,即使服务端返回 InstanceStatus=PAUSED,文本也始终是 Pause requested: <id>。当前两个 channel 都走 typed,该问题处于潜伏状态。tool fork 的 createdToolID 已经为两种类型各写了一个分支。
  • JSON 形状不同:typed 结果经 SDK struct 重新序列化(omitnil,omitempty),契约外的扩展字段会丢失,null 字段会被省略;dynamic 保留服务端原始字段、null 和整数精度。省略 null 这一条来自源码推断,尚未用真实响应对比。

两条路线的传输能力等价:都基于 SDK common 包签名,网络与限流重试都是 0,错误都经 ClassifyCloudError 分类;请求在 Call 入口统一执行契约 ValidateRequest。typed 路线不提供额外的校验或传输能力。

方案

所有控制面 Action 统一走“契约校验 + 签名 JSON”传输(即现有 dynamic 路线),取消路线选择:

  • SDK.Call 固定返回 apivalue.Object。
  • 删除 sdkShapes、needsDynamic 及兼容判定、typed 分发 switch、call* / fillRequest、按 Action 注入的函数字段,以及 Client / NewClient / cloudClient / cacheInstanceToken。测试统一通过已有的 RawSender 注入响应。
  • StartSandboxInstance 成功后的 token 缓存只保留一份实现(cacheResourceToken)。workflow Action 移除 RequestId、错误分类和诊断上下文的行为保持不变。
  • 读取 typed 返回值的使用方改为读 Object,例如 instance pause 的文本输出、tool fork 的 ToolId 提取。
  • internal/cli 中直接调用 typed SDK 的路径改为经 controlplane.SDK.Call:overlay 临时实例的创建和删除、doctor 连通性探测、实例 token 获取。删除 cloud_calls.go 及对应的 Cloud* / SetCloud*ForTest 测试 seam。

依赖边界:

  • 保留 tencentcloud/common:签名、临时凭据和错误类型由它提供,不自行实现。
  • tencentcloud/ags 不再用于传输,但仍用于展示投影(通过 apivalue.Project 投影到 ags.* struct,例如 instance、deployment、apikey 视图)。是否移除该依赖见“后续”,不在本 Issue 范围。

建议拆成两个 PR:① controlplane 收敛加使用方修正;② internal/cli 的 typed 路线切换。

兼容性

原来走 typed 的 Action(stable 19 个、preview 18 个)的 -o json 输出,可能从“字段缺失”变为保留服务端原样的 null 和扩展字段(推断,未比对真实响应)。依赖字段是否存在的 --jq 脚本可能受影响。实施时需要:

  • 用包含 null、空数组和契约外字段的固定 mock 响应,对上述 Action 生成新旧输出对比,在 PR 中列出差异;
  • Release impact 选择 Stable and preview,并在 Changelog 中说明。

验收标准

  • 生产代码不再构造 ags.Client,也不再调用其方法;internal/controlplane 不再 import tencentcloud/ags,并由 lint 规则(如 depguard)约束,不依赖手写清单。
  • 新增契约驱动的全量路由测试:遍历 stable/preview 当前契约中的每个 Action,用最小合法请求调用 SDK.Call,断言恰好发送一次签名 JSON、Action 正确、返回 apivalue.Object。新增契约 Action 时无需修改测试即被覆盖。
  • instance pause 在响应 InstanceStatus=PAUSED 时,文本输出为 Instance paused: <id>(回归测试)。
  • instance create 的普通创建、--source-instance-id 和 --snapshot-id(feat(instance): add sandbox snapshots and source cloning #148 合入后)各自恰好缓存一次 token;AuthMode=NONE 时不获取 token;token 获取失败只输出警告,不使命令失败。
  • overlay 临时实例(instance exec、instance code run)的创建与清理,以及 doctor 连通性检查,经统一路径执行且行为不变。
  • 保留上下文取消的错误分类、云 API 错误码与 RequestId、超出 int64 范围的整数精度和临时凭据支持。
  • 新旧 JSON 输出差异已列入 PR 并写入 Changelog。
  • stable 和 preview 下 go test -race ./...、golangci-lint、go run ./cmd/internal/cobragen check、go run ./cmd/internal/apipatch check-all 以及 mock HTTPS 集成测试全部通过。
  • 对传输路线实际发生变化的资源族完成真实 E2E 和清理回查:tool create/update/delete、instance update/pause/resume/stop/token、apikey create/list/delete、deployment 生命周期(含 proxy token)、pre-cache image task create/get;第二个 PR 补 overlay 临时实例和 doctor。未覆盖项如实报告。

范围与关联

后续(不在本 Issue)

展示投影仍依赖 tencentcloud/ags 的 struct:SDK 落后于契约时,新字段会被丢弃,或者投影失败、降级为原样打印。是否改为契约生成的类型或直接读取 Object,并据此移除 tencentcloud/ags 依赖,与 #146 第 4 步一起决策。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions