fix: align runtime create payload with agnet
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Heicode Manager 可独立执行任务计划
|
||||
|
||||
更新时间:2026-05-26
|
||||
更新时间:2026-05-27
|
||||
负责人范围:Heicode Manager 端
|
||||
用途:后续开发按本文逐项执行、验收和更新状态。
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
| 11 | 前端页面补齐独立闭环展示 | P1 | 是 | 用户能看到任务、deployment、事件、artifact、审批和审计关系 |
|
||||
| 12 | 文档口径清理 | P2 | 是 | 避免旧 Vault/OpenBao/Secret Provider 表述误导 |
|
||||
| 13 | AWS/GCP 占位提示 | P2 | 是 | 避免用户误以为 AWS/GCP 已可用 |
|
||||
| 14 | 请求 body 加密策略确认 | P2 | 是 | 明确 HTTPS 传输加密、应用层 body 加密是否需要、敏感字段禁止入参和日志 |
|
||||
|
||||
## 四、任务明细
|
||||
|
||||
@@ -252,6 +253,24 @@
|
||||
- 禁用项不能提交到后端创建真实资源发现。
|
||||
- 已有 Azure 流程不受影响。
|
||||
|
||||
### 任务 14:请求 body 加密策略确认
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 目标 | 明确桌面客户端调用 Manager 时,请求体当前依赖 HTTPS/TLS 传输加密;是否需要额外应用层 body 加密需单独评估,不默认混入 sub 敏捷接口 |
|
||||
| 修改文件 | `docs/integration/heicode-desktop-sub-agile-api.md`、`docs/heicode-runtime-auth-newapi-secret-design.md`、必要时补充 `heicode/middleware` 或客户端 SDK 加解密模块 |
|
||||
| 当前现实 | 生产入口应使用 `https://code.xinghanlab.com`;JSON body 在网络传输中由 TLS 加密,到 Manager 后端后按普通 JSON 解析 |
|
||||
| 安全红线 | 请求体、回调、日志、artifact metadata、timeline 中禁止出现明文密码、Token、私钥、连接串、云 access key、模型 key;长期凭证只传 `secret_ref` |
|
||||
| 后续判断 | 如有合规要求再设计应用层加密,例如请求体 AES-GCM + 短期会话密钥/设备公钥;同时要设计密钥分发、轮换、重放保护、版本兼容和排障方案 |
|
||||
| 测试 | 检查生产 API 只走 HTTPS;敏感字段扫描;单测覆盖疑似明文凭据拒绝;如实现应用层加密,再补加解密兼容测试 |
|
||||
|
||||
验收标准:
|
||||
|
||||
- 文档明确“HTTPS 传输加密已覆盖 body 在网络中的保密性”,不误导为当前已有业务层 body 加密。
|
||||
- 客户端对接文档明确禁止传明文长期凭据,只允许 `secret_ref` / `lease://...` / 资源元数据。
|
||||
- 服务端日志不打印请求 body 中的敏感字段。
|
||||
- 如果后续实现应用层 body 加密,必须保留版本字段和灰度开关,避免旧客户端直接不可用。
|
||||
|
||||
## 五、推荐执行批次
|
||||
|
||||
### 批次 A:最小 Manager 闭环
|
||||
@@ -285,6 +304,7 @@
|
||||
| 3 | 前端闭环展示 |
|
||||
| 4 | 文档口径清理 |
|
||||
| 5 | AWS/GCP 占位提示 |
|
||||
| 6 | 请求 body 加密策略确认 |
|
||||
|
||||
完成批次 C 后,Manager 端应具备清晰的任务视角、持久化上下文、准确页面口径和更少误导。
|
||||
|
||||
@@ -333,6 +353,7 @@ Manager 独立任务完成,不等于蜂群生产闭环完成。本文完成的
|
||||
| 幂等 | 重复回调不重复写入 |
|
||||
| 审计 | task/deployment/correlation_id 下能聚合审计、审批、artifact、callback |
|
||||
| 安全 | API、日志、页面不出现明文长期密钥 |
|
||||
| 请求加密 | 文档明确当前依赖 HTTPS/TLS 传输加密;应用层 body 加密如需实现,有清晰设计和兼容策略 |
|
||||
| 口径 | 页面和文档不把本地占位/模拟事件说成真实 Runtime |
|
||||
|
||||
## 八、执行时不能突破的边界
|
||||
@@ -344,4 +365,4 @@ Manager 独立任务完成,不等于蜂群生产闭环完成。本文完成的
|
||||
5. 不把本地模拟事件当真实蜂群完成证据。
|
||||
6. 不把 Azure Key Vault 暴露成普通用户要直接操作的后台。
|
||||
7. 不绕过客户端高危审批的产品边界;Manager 只能先提供记录和 API。
|
||||
|
||||
8. 不在未完成密钥协商、轮换和兼容设计前,临时硬塞应用层 body 加密,避免客户端和 Manager 联调不可排障。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@
|
||||
|
||||
**阻塞解除条件**:
|
||||
- agent-manager 侧给 mcp-server 派发 service token
|
||||
- mcp-server 把 stub 换成对 `20.212.121.126:80` 的真实转发(HTTP/80,Host 头 `agent-manager.taijiagnet.com`)
|
||||
- mcp-server 把 stub 换成对 `http://20.212.121.126` 的真实转发;域名 `agent-manager.taijiagnet.com` 的 DNS / HTTPS 后续单独修复,不作为当前联调阻塞项
|
||||
|
||||
Manager 前端已按 §5 契约消费,stub → 真实数据无需前端改动。
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.4.5
|
||||
1.4.6
|
||||
|
||||
@@ -359,6 +359,15 @@ func TestAgnetRuntimeShadowCreateStoresRuntimeMapping(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), `"sub_mode":"agile"`)
|
||||
require.Contains(t, string(body), `"manager_deployment_id"`)
|
||||
var runtimeBody map[string]any
|
||||
require.NoError(t, common.Unmarshal(body, &runtimeBody))
|
||||
agents := runtimeBody["agents"].([]any)
|
||||
require.Equal(t, "builder", agents[0].(map[string]any)["role"])
|
||||
metadata := runtimeBody["metadata"].(map[string]any)
|
||||
require.NotEmpty(t, metadata["manager_deployment_id"])
|
||||
grants := runtimeBody["resource_grants"].([]any)
|
||||
require.NotEmpty(t, grants)
|
||||
require.NotEmpty(t, grants[0].(map[string]any)["ref"])
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-1","swarm_id":"swarm-1","runtime_status":"accepted"}}`))
|
||||
}))
|
||||
|
||||
@@ -128,6 +128,123 @@ func agnetRuntimeHeaders(req *http.Request, cfg agnetRuntimeConfig, record agnet
|
||||
}
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestAgents(plan agnetOrchestrationPlan) []gin.H {
|
||||
byRole := make(map[string]agnetAgentPlan, len(plan.Agents))
|
||||
for _, agent := range plan.Agents {
|
||||
role := strings.TrimSpace(agent.RoleTemplate)
|
||||
if role != "" {
|
||||
byRole[role] = agent
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]gin.H, 0, len(plan.AgentRuntime.Agents))
|
||||
for _, runtimeAgent := range plan.AgentRuntime.Agents {
|
||||
role := strings.TrimSpace(runtimeAgent.Role)
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
item := gin.H{"role": role}
|
||||
if agent, ok := byRole[role]; ok {
|
||||
if len(agent.SKSources) > 0 {
|
||||
item["sk_sources"] = agent.SKSources
|
||||
}
|
||||
if len(agent.ResourceGrants) > 0 {
|
||||
item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
return items
|
||||
}
|
||||
for _, agent := range plan.Agents {
|
||||
role := strings.TrimSpace(agent.RoleTemplate)
|
||||
if role == "" {
|
||||
continue
|
||||
}
|
||||
item := gin.H{"role": role}
|
||||
if len(agent.SKSources) > 0 {
|
||||
item["sk_sources"] = agent.SKSources
|
||||
}
|
||||
if len(agent.ResourceGrants) > 0 {
|
||||
item["resource_grants"] = agnetRuntimeResourceGrantPayloads(agent.ResourceGrants)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func agnetRuntimeResourceGrantPayloads(grants []agnetResourceGrant) []gin.H {
|
||||
items := make([]gin.H, 0, len(grants))
|
||||
for _, grant := range grants {
|
||||
secretRef := strings.TrimSpace(grant.SecretRef)
|
||||
if secretRef == "" {
|
||||
continue
|
||||
}
|
||||
item := gin.H{
|
||||
"grant_id": grant.GrantID,
|
||||
"resource_id": grant.ResourceID,
|
||||
"resource_type": grant.ResourceType,
|
||||
"type": grant.ResourceType,
|
||||
"user_id": grant.UserID,
|
||||
"binding_scope": grant.BindingScope,
|
||||
"target_role": grant.TargetRole,
|
||||
"target_agent_ref": grant.TargetAgentRef,
|
||||
"permission_scope": grant.PermissionScope,
|
||||
"permissions": grant.PermissionScope,
|
||||
"status": grant.Status,
|
||||
"ref": secretRef,
|
||||
"secret_ref": secretRef,
|
||||
"allowed_ref": agnetGrantResourceRef(grant),
|
||||
"resource_ref_hint": agnetGrantResourceRef(grant),
|
||||
}
|
||||
if grant.TenantID != "" {
|
||||
item["tenant_id"] = grant.TenantID
|
||||
}
|
||||
if grant.ProjectID != "" {
|
||||
item["project_id"] = grant.ProjectID
|
||||
}
|
||||
if len(grant.Constraints) > 0 {
|
||||
item["constraints"] = grant.Constraints
|
||||
}
|
||||
if len(grant.Metadata) > 0 {
|
||||
item["metadata"] = grant.Metadata
|
||||
}
|
||||
if len(grant.Audit) > 0 {
|
||||
item["audit"] = grant.Audit
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestResourceGrants(plan agnetOrchestrationPlan) []gin.H {
|
||||
items := make([]agnetResourceGrant, 0, len(plan.ResourceGrants))
|
||||
items = append(items, plan.ResourceGrants...)
|
||||
for _, agent := range plan.Agents {
|
||||
items = append(items, agent.ResourceGrants...)
|
||||
}
|
||||
return agnetRuntimeResourceGrantPayloads(items)
|
||||
}
|
||||
|
||||
func agnetRuntimeRequestMetadata(record agnetDeploymentRecord, source string) gin.H {
|
||||
metadata := gin.H{
|
||||
"correlation_id": record.Plan.Metadata.CorrelationID,
|
||||
"manager_deployment_id": record.DeploymentID,
|
||||
"source": source,
|
||||
"heicode_deployment_id": record.DeploymentID,
|
||||
"heicode_runtime_bridge": true,
|
||||
}
|
||||
if record.Plan.Metadata.TenantID != "" {
|
||||
metadata["tenant_id"] = record.Plan.Metadata.TenantID
|
||||
}
|
||||
if record.Plan.Metadata.ProjectID != "" {
|
||||
metadata["project_id"] = record.Plan.Metadata.ProjectID
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func agnetRuntimeCreatePayload(record agnetDeploymentRecord, source string) gin.H {
|
||||
callback := gin.H{
|
||||
"url": agnetRuntimeCallbackURL(),
|
||||
@@ -137,11 +254,16 @@ func agnetRuntimeCreatePayload(record agnetDeploymentRecord, source string) gin.
|
||||
callback["signing_secret_ref"] = ref
|
||||
}
|
||||
return gin.H{
|
||||
"deployment_id": record.DeploymentID,
|
||||
"manager_deployment_id": record.DeploymentID,
|
||||
"source": source,
|
||||
"callback": callback,
|
||||
"orchestration_plan": record.Plan,
|
||||
"orchestration_plan": record.Plan,
|
||||
"agents": agnetRuntimeRequestAgents(record.Plan),
|
||||
"risk_level": record.Plan.RiskLevel,
|
||||
"budget": record.Plan.Budget,
|
||||
"billing_context": record.Plan.BillingContext,
|
||||
"resource_grants": agnetRuntimeRequestResourceGrants(record.Plan),
|
||||
"callback": callback,
|
||||
"agile_context": record.Plan.AgileContext,
|
||||
"sub_mode": record.Plan.SubMode,
|
||||
"metadata": agnetRuntimeRequestMetadata(record, source),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,14 @@ services:
|
||||
# the VM managed identity. No static secret-store token is required.
|
||||
- AZURE_KEY_VAULT_URL=${AZURE_KEY_VAULT_URL:-}
|
||||
- AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}
|
||||
# Agent Manager Runtime: use the current production IP directly.
|
||||
# The public domain is not used until DNS/HTTPS is fixed.
|
||||
- AGNET_RUNTIME_ENABLED=${AGNET_RUNTIME_ENABLED:-false}
|
||||
- AGNET_RUNTIME_BASE_URL=${AGNET_RUNTIME_BASE_URL:-http://20.212.121.126}
|
||||
- AGNET_RUNTIME_CREATE_PATH=${AGNET_RUNTIME_CREATE_PATH:-/api/agnet/deployments}
|
||||
- AGNET_RUNTIME_HEALTH_PATH=${AGNET_RUNTIME_HEALTH_PATH:-/api/agnet/health}
|
||||
- AGNET_RUNTIME_SERVICE_TOKEN=${AGNET_RUNTIME_SERVICE_TOKEN:-}
|
||||
- AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF=${AGNET_RUNTIME_CALLBACK_SIGNING_SECRET_REF:-}
|
||||
networks:
|
||||
- heicode-network
|
||||
healthcheck:
|
||||
|
||||
@@ -125,6 +125,15 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
require.Contains(t, string(body), `"manager_deployment_id"`)
|
||||
require.Contains(t, string(body), `"signing_secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/callback-signing"`)
|
||||
require.Contains(t, string(body), `"subscribed_events"`)
|
||||
var runtimeBody map[string]any
|
||||
require.NoError(t, common.Unmarshal(body, &runtimeBody))
|
||||
agents := runtimeBody["agents"].([]any)
|
||||
require.Equal(t, "builder", agents[0].(map[string]any)["role"])
|
||||
grants := runtimeBody["resource_grants"].([]any)
|
||||
require.NotEmpty(t, grants)
|
||||
require.NotEmpty(t, grants[0].(map[string]any)["ref"])
|
||||
metadata := runtimeBody["metadata"].(map[string]any)
|
||||
require.NotEmpty(t, metadata["manager_deployment_id"])
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-http-dep","swarm_id":"runtime-http-swarm","runtime_status":"accepted"}}`))
|
||||
default:
|
||||
@@ -159,7 +168,7 @@ func TestAgnetRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) {
|
||||
"billing_context":{"provider":"newapi","newapi_user_ref":"newapi-http-smoke"},
|
||||
"agile_context":{"iteration":"2026-05-27~2026-05-28","stage":"development","checkpoint":"ready_for_test","acceptance_criteria":["接口返回成功"],"next_action":"submit_test_result","requires_user_approval":false},
|
||||
"agent_runtime":{"platform":"agnet","agents":[{"role":"builder","model_ref":"model-http-smoke","instance_count":1}]},
|
||||
"agents":[{"role_template":"builder","goal":"smoke","default_model_id":"model-http-smoke","resource_grants":[{"grant_id":"grant-http-doc","resource_id":"doc-http","resource_type":"project_doc","user_id":"101","binding_scope":"task-http-smoke","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["doc:read"],"status":"active"}]}],
|
||||
"agents":[{"role_template":"builder","goal":"smoke","default_model_id":"model-http-smoke","resource_grants":[{"grant_id":"grant-http-git","resource_id":"git-http","resource_type":"git","user_id":"101","binding_scope":"https://example.invalid/heicode/smoke.git#main","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["repo:read"],"metadata":{"repo_url":"https://example.invalid/heicode/smoke.git"},"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/http-smoke-git","status":"active"}]}],
|
||||
"constraints":{"allowed_model_ids":["model-http-smoke"]},
|
||||
"metadata":{"correlation_id":"corr-http-smoke"}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user