函数注册 [UI 生成链路审核(2026-09]
状态说明:本文档为审核落盘 + 修复进度跟踪。所有 file:line 已逐条人工核对(2026-09-19)。 「修复批次」列:
B1=本批修复,backlog=记录待办不修。中断后按状态列续作。
0. 链路全景
游戏服 SDK ──TCP:19091──▶ Agent(本地登记 agentlocal) ──TCP:19090──▶ Server ControlService(校验+物化)
│
内存 registry ──┬── agent_sessions 表(meta)
├── FunctionContract 表(权威 UI 事实源)
└── PageProposal(页面提案,UI 生成入口)
Web ──HTTP:18780──▶ /api/v1/functions/descriptors(FunctionSpec wire,全 lowerCamel)
├─【调试通道】Invoke 工作台:derivePresentationSpec(x-ui-* hints) → SchemaFormRenderer(@rjsf/antd+ajv8) → invoke
└─【发布通道】服务端 generator 同源推导 → PageProposal → accept-and-publish → PublishedPageSpec → PageRenderer → binding/execute设计事实:注册契约刻意不含 UI 字段(proto/croupier/agent/v1/register.proto 注释);表单唯一来源是 inputSchema 内受控的 x-* hints(docs/architecture/presentation-hints.md);CLAUDE.md 所称「pack .tgz 携带 UI plugins 远程加载」不存在于代码(全仓无 tar/UI plugin),自定义 UI 已收敛为纯 spec + 受控 widget 枚举。
1. 阶段一:注册链路(后端)
| # | 环节 | 关键点 | 卡点(已核对) | 批次 | 状态 |
|---|---|---|---|---|---|
| 1.1 | 注册错误黑洞 ✅已修 | internal/server/tcp_listener.go:364-369 吞掉 handleRegisterRequest 全部错误(含契约物化失败),仍回成功 | agent 以为注册成功;registry 无函数表 → 调用端只见 "no live agent"。且 internal/app/agent/upstream.go syncOnce 完全不读 resp.Warnings(proto 有 warnings 字段,register.proto RegisterResponse),成功路径的校验告警也被静默丢弃 | B1 | done |
| 1.2 | deprecated 断链 ✅已修 | agentlocal/store.go:153 已保留 Deprecated;proto agentv1.FunctionDescriptor.deprecated=28 | upstream.go:505-538 syncOnce 组装时不拷贝 meta.Deprecated → server control_handler.go:522 附近 FunctionMeta 构造读 f.GetDeprecated()(547 行仅 openapi 路径)→ 契约表 deprecated 恒 false | B1 | done |
| 1.3 | timeoutMs 漏投影 ✅已修 | 模型 function_contract.go:27 TimeoutMs 存在,迁移 0016 已配(svc/migrations.go:253,MinimumRequiredVersion=28) | internal/service/contract_projection.go:54-78 FunctionSpecFromContract 不设 TimeoutMs → /functions/descriptors 的 timeoutMs 恒空(执行层直读 DB 不受影响,仅 UI 侧不可见)。同批补 backfillInputFromClassification 的 TimeoutMs 保留(SDK 重注册不带该字段,此前会把 OpenAPI 已声明预算清零——实施中新发现的姊妹缺口) | B1 | done |
| 1.4 | policy risk 键错位 ✅已修 | internal/platform/openapi/converter.go:58 写 extension x-risk | internal/api/function/helpers.go:1443 enforceFunctionPolicy 读 x-risk-level → 永不命中,所有函数策略 risk 静默默认 medium(helpers.go:1440);且词表不同(契约 safe/warning/high/danger ↔ policy low/medium/high/danger),需映射 | B1 | done |
| 1.5 | semver 硬门槛(部分处理) | control_handler.go:1113-1117 版本非三段 semver 整函数丢弃(仅 warning) | OpenAPI provider 路径已修(2026-09-19):config.version 可配 + 解析链 x-version > config.version > info.version > 1.0.0(versionutil 归一,非法值告警回退不丢函数);server 侧 OpenAPI 绑定物化同步归一。扩展注册用 item.ReleaseVersion(app/agent/app.go:562)仍直传,非 semver 发布号函数消失 | backlog(剩余:extension 路径) | — |
| 1.6 | SDK provider scope 校验形同虚设 | tcp_local_listener.go:267 connect 不校验 game/env;app.go:177-178 onConnect 用 agent scope 回填 metadata | server validateProviderScope(control_handler.go:850)对 SDK provider 恒通过;local_handler.go:763-866 的校验在生产装配下不可达 | backlog | — |
| 1.7 | 静态注册者恒报 mismatch | provider:(provider.go:186)与 extension:(app.go:562)metadata=nil | 每次注册触发 provider_scope_mismatch 噪音警告 | backlog | — |
| 1.8 | 警告/操作表纯内存 | platform/registry/store.go:139-141 registrationWarnings、openapiOperations 不落库 | 重启后 /functions/warnings 历史丢失、/functions/:id/openapi 依赖 agent 重注册回填 | backlog | — |
| 1.9 | FunctionsList 版本字符串比较 | functions_list_logic.go 与 upstream.go pickVersion 用 > 字符串序 | "1.9.0" > "1.10.0" 判错 | backlog | — |
| 1.10 | SDK 滑动版本门槛 | control_handler.go:955-977 落后 provider 独占函数整体剔除 | 「函数凭空消失」仅内存 warning 可见(1.8 关联) | backlog | — |
2. 阶段二:UI 生成——调试通道(Invoke 工作台)
| # | 环节 | 关键点(已核对) | 批次 | 状态 |
|---|---|---|---|---|
| 2.1 | 审批续跑冲突 ✅已修 | 服务端 approve 即自动重放原 payload(internal/api/approval/service.go:225-254 continueApprovedFunction),响应 DTO 带 result/taskId/resultKind/continuation(dto.go:21,84,104);前端 web/src/pages/Functions/Invoke/index.tsx:350-353 轮询只取 {status,reason} 丢弃续跑结果,:568-575 反而提供「重新调用」按钮 → 用户再点即第二次真实副作用(且 invoke 无幂等键通道 functions.ts:237-254)。发布页链路是对的(OperationPageRenderer 展示续跑结果不给重放按钮)→ 两链行为不一致 | B1 | done |
| 2.2 | 无错误边界 ✅已修 | 全 web/src 无 ErrorBoundary/componentDidCatch;schema 畸形($ref 缺失、items 非 object)时 RJSF 渲染抛错 = 整页白屏。现有降级只覆盖「schema 缺失」(formState.status:'unavailable' → Alert + JSON tab)。已修:SchemaFormRenderer 内置 ErrorBoundary(Alert 降级+重试按钮;spec 变化经 key 自动复位),一处包裹覆盖全部调用点(调试通道+发布通道五个渲染器) | B1 | done |
| 2.3 | descriptor 兜底链 | functions.ts:145-158 inputSchema ?? input ?? descriptor.input 三级兜底;v1({items:[{input,output}]})/v2({functions})双形态并存 | backlog | — |
| 2.4 | hints 只扫顶层 | schemaHints.ts:236-242 collectFields 仅顶层 properties;嵌套字段 x-widget/visibleWhen 无效;asEnumOptions(:130) 要求 value 为 string,整数枚举 label 全丢 | backlog | — |
| 2.5 | ajv draft-07 | @rjsf/validator-ajv8 按 draft-07 编译,2020-12 关键字(prefixItems/dependentRequired/$dynamicRef)静默忽略;TS dashboard.ts:50 注释却写「draft-07 / 2020-12」;protoc-gen-croupier 产的是 2020-12 schema——生成侧与校验侧版本错配 | backlog | — |
| 2.6 | 治理字段事前无感 | wire 有 approval/permission/risk/executionState/timeoutMs,TS FunctionDescriptor(functions.ts:14-38)未声明未消费:下拉不标注高危/需审批/unbound,选中 unbound 照发拿非结构化失败 | backlog(1.3 修复后至少 timeoutMs 可见) | — |
| 2.7 | 死字段/孤儿代码 | TS FunctionInvokeResponse 的 duration/timestamp/error 后端不存在(functions.ts:178-189 vs dto.go:180-196);broadcast 后端有前端无;plugin/registry.tsx+transform.ts 零生产引用;PermissionGuard/useAnyPermission 零引用 | backlog | — |
3. 阶段三:UI 生成——发布通道(Proposal [PublishedPageSpec]
| # | 环节 | 关键点(已核对) | 批次 | 状态 |
|---|---|---|---|---|
| 3.1 | FormFieldSpec 三字段静默丢弃 ✅已修 | 类型双侧齐备:Go internal/dashboard/spec/form_presentation.go:79-96(Required/DefaultValue/ValidationRules),TS dashboard.ts:799-805;Page Studio manual-merge 写路径支持发布(service/versioning/service.go:1365-1393);但 SchemaFormRenderer/index.tsx:239-297 applyFieldPresentation 只消费 label/placeholder/description/enumOptions/disabled/widget/remoteOptions/widgetProps——编辑器配置"看起来成功",运行无声消失。已修:mergeFieldValidity(required/validationRules→ajv 关键字,custom 显式告警)+ applySpecDefaults(defaultValue 种入初值,显式值优先);wire 语义补进 pagespec-protocol.md | B1 | done |
| 3.2 | 提案重建失败静默 | 注册提交后异步重建(registry/store.go:531-569),失败降级 warning;需手动 POST /pages/proposals/rebuild 兜底,无告警面 | backlog | — |
| 3.3 | 破坏性 schema 变更只告警 | F12 双处(control_handler.go:585-618 内存基线 + contract_service.go:1002-1036 DB 基线);绑定页面在 freshness 校验前继续按旧 PageSpec 渲染 →「注册成功但 UI 字段对不上」 | backlog(依赖产品决策:阻断 or 醒目提示) | — |
| 3.4 | ActionSpec.permission 不消费 | 发布页按钮人人可见,点击才服务端 403;requestErrorConfig.ts:210-228 兜底跳 /403 | backlog | — |
| 3.5 | 审批中心 risk 词表错位 | pages/Approvals/index.tsx 筛选项给 medium/low(策略引擎词表),SDK 契约 descriptor 是 safe/warning 词表——两个下拉互匹配不上;强确认只判 risk==='high' 不覆盖 danger(默认策略 danger 才是 two_person 最高级,policy/manager.go:127-131) | backlog(根治需统一 risk 词表,涉及 policy 引擎+迁移,本批不动) | — |
| 3.6 | widget 声明与实现不齐 | 受控枚举允许 RichText/DateRange,widgetToRjsf(index.tsx:227-236) 无实现 → 静默降级文本框 | backlog | — |
| 3.7 | 远程选项失败静默 | useRemoteOptions.ts:99-103 403/5xx/网络统一吞成空选项,无权限与无数据不可分辨 | backlog | — |
4. 本批修复(B1)清单与验收
- 1.1 注册错误显式化:
tcp_listener.go物化失败时把错误写入 RegisterResponse.Warnings;upstream.gosyncOnce 读取并 slog.Warn resp.Warnings。不动「回成功」语义(避免破坏 agent 重连风暴),先保证可见性。验收:Go 单测(注册失败→warnings 含错误;成功→透传校验 warnings)。 - 1.2 deprecated 链路补全:
upstream.gosyncOnce 拷贝meta.Deprecated → desc.Deprecated;确认control_handler.goFunctionMeta 构造读取f.GetDeprecated(),未读则补。验收:agentlocal→server FunctionMeta→contract 的传递测试。 - 1.3 timeoutMs 投影:
contract_projection.goFunctionSpecFromContract 补TimeoutMs: int(contract.TimeoutMs)。验收:投影单测。 - 1.4 policy risk 键错位:
helpers.go:1443改读x-risk并做词表映射(safe→low、warning→medium、high→high、danger→danger;未知值回落 medium 保持现状)。验收:单测覆盖映射。 - 2.1 审批续跑对齐:
web/src/services/console.tsqueryApprovalStatus 透出result/taskId/resultKind/continuation;Invoke/index.tsxapproved+continuation 时展示结果(sync→InvocationResponse;task→TaskProgressPanel),移除「重新调用」按钮(改为「查看结果/前往审批单」)。对齐发布页行为。验收:组件测试更新。 - 2.2 表单错误边界:新增轻量
SchemaFormErrorBoundary组件包裹 SchemaFormRenderer 全部调用点,抛错降级为 Alert + 提示切 JSON 模式(发布页给结构化空态)。验收:抛错场景测试。 - 3.1 FormFieldSpec 三字段生效:
applyFieldPresentation消费required(并入 runtime schema.required)、defaultValue(参与初值合成,优先级 spec 覆盖 schema default)、validationRules(required/min/max/pattern 翻译进 schema 的 min/max/pattern + required;custom 忽略并 console.warn)。验收:渲染器单测。
DoD 检查(交付前):go build ./...、go test ./internal/...(至少触及包)、gofmt -l . 空、pnpm --dir web run tsc 0 错误、web 相关 jest 用例、bash scripts/dashboard_vnext_guard.sh(仓库根)、文档同步(presentation-hints.md / ui-generation.md 补「required/defaultValue/validationRules 已生效」与 risk 映射说明;CLAUDE.md pack 表述纠偏)。
4A. B2 立项:函数契约版本历史 [已实施(2026-09-19,落地与设计一致,差异见交付记录]
现状缺口:function_contracts 每函数仅一行(唯一索引 (game_id,env,function_id)),历史只有 prev_*_schema 一步回看;CapabilitySemanticVersion 是资源语义的版本表先例,函数契约无对应物。
设计(已与用户对齐):
- 以函数 id 为主维度,每次内容变化(
source_digest比对,重复注册相同内容不写)追加一条:新表function_contract_versions:(game_id, env, function_id, seq, version, source, source_digest, snapshot_json, change_type created|updated|removed, diff_json, breaking, actor, created_at)。 - 写路径:
rebuildContract/RemoveFunctionContract与契约写同事务落 history(多库模式随既有持久操作记录+补偿路径)。 - API:
GET /functions/:id/versions(分页列表)、GET /functions/:id/versions/:seq(快照)、GET /functions/:id/versions/diff?from=&to=(字段级 diff,复用internal/function/schemadiff)。 - UI:函数详情页「变更历史」tab。
- 保留策略(用户已定):每函数上限 N 条(实现为常量 50:
FunctionContractVersionRetention,同事务内裁剪),超限按最老淘汰。 - 实现差异(相对草案):①
seq不设唯一索引(多实例 HA 并发注册下唯一冲突会毒化注册事务;读取按 seq,id 稳定排序);② 历史写失败降级为告警不阻断注册(与提案/模板重建同档的衍生数据先例;表存在性由启动期 MinimumRequiredVersion=29 兜底);③ diff 端点按两版快照现算,不拼接链上 diff。 - 迁移:goose 0029(MinimumRequiredVersion 28→29)+ 三处同步(svc/migrations.go、db/migrate/migrate.go、migrate_test.go 合成清单)+ DropTable 模拟老库回归用例(CLAUDE.md 迁移契约)。
5B. B2 交付记录(2026-09-19)
- 新增:
internal/model/function_contract_version.go(表+数据访问:AppendVersion/seq/裁剪/分页/单查)、internal/service/contract_history.go(判等/快照/diff/breaking 计算)、internal/logic/function/contract_versions_logic.go+internal/api/function/contract_versions_handler.go(三端点,scoped 组注册routes.go)、迁移 0029 三处同步 +TestGoMigrations_ContractVersionsCatchUp老库回归。 - 写路径:
rebuildContract(created/updated,判据=model.ContractSemanticallyEqual 同一函数)与RemoveFunctionContract(removed+删除前快照)挂钩;model.GameModels()注册新表。 - Web:
services/api/functions.ts三函数(未知 changeType 透传不静默改写)、函数详情页新增「变更历史」tab(VersionsTab:列表/快照 Drawer/两版对比含 breaking 告警)、locale zh/en 全键。 - 测试:service 3 组(生命周期含去重/breaking/removed、保留裁剪 60→50、列表瘦身)、logic 5 组(分页/详情/diff/scope 隔离/wire key)、jest service 5 用例 + VersionsTab 5 用例。
- 文档:
dashboard-page-model.md(FunctionContractVersion 模型节 + prev 列「无版本表」表述修正)、docs/api/function.md§22-24。
5. 交付记录(B1,2026-09-19)
- Go:
go build ./...通过;go test ./internal/...全绿;gofmt -l空。新用例:internal/server/tcp_listener_test.go(materialize 失败经 warnings 可见 + 校验 warnings 透传)、internal/app/agent/coverage_v9b_test.go(deprecated 透传)、internal/api/function/coverage_v9_test.go(x-risk 映射 + 契约行兜底/safe 歧义/scope 缺失)、internal/service/contract_projection_test.go(timeoutMs 投影)、internal/service/contract_service_test.go(backfill 保留 timeout)。 - Web:
pnpm --dir web run tsc0 错误;jest 相关域全绿(SchemaFormRenderer 14 套件/183 用例、Invoke 7 套件/113 用例、PageRenderer+services 56 套件/840 用例)。新用例:field-validity.test.tsx(14)、error-boundary.test.tsx(3)、approvalPolling.test.ts透传回归、Invoke 审批流 4 例改写/新增。 - Guard:
bash scripts/dashboard_vnext_guard.shPASSED。 - 文档:
docs/architecture/pagespec-protocol.md补渲染器生效语义;docs build见下方状态。 - 行为变更提示(上线注意):1.4 修复后,契约声明
high/danger的函数将首次真实命中审批/角色默认策略(此前全量按 medium 静默评估)——属「声明即契约」恢复,但可能让存量声明首次生效;发布前应抽查线上function_contracts.risk ∈ {high,danger}行的策略影响面。 - 未完成项(边界诚实):Docker 构建 + deploy-self-hosted + CI Core 全绿未在本会话执行,不宣称上线;PageSpec 发布链线上 smoke 未跑(B1 改动不改 spec 字段形态)。
6. backlog 汇总(不在本批)
semver 硬门槛(1.5)、provider scope 双路径(1.6)、静态注册者噪音(1.7)、warning/openapi 内存态(1.8)、版本字符串比较(1.9)、SDK 门槛剔函数可观测性(1.10)、v1/v2 双形态(2.3)、嵌套 hints(2.4)、ajv draft 错配(2.5)、治理字段事前标注(2.6)、死字段清理(2.7)、提案重建告警(3.2)、破坏性变更 UI 阻断(3.3)、ActionSpec.permission(3.4)、risk 词表根治(3.5)、widget 枚举补实现(3.6)、远程选项报错(3.7)、CLAUDE.md pack 文档纠偏(部分并入本批文档项)。
