From 0353215b1d3ccbc049a2df2b0a18deea9818412e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E6=BE=9C?= Date: Thu, 6 Aug 2026 14:52:34 +0800 Subject: [PATCH] Revert "declare(ding): semantic ParamDecl backfill for schema inference residuals" This reverts commit 969292a8d72f86be454c5ea615652cf6fdc4823b. --- internal/cli/schema_catalog_test.go | 54 ---------- internal/helpers/ding.go | 9 +- internal/shortcut/ding/ding.go | 21 ---- scripts/policy/schema-compat/main.go | 63 +----------- scripts/policy/schema-compat/main_test.go | 120 +--------------------- 5 files changed, 6 insertions(+), 261 deletions(-) diff --git a/internal/cli/schema_catalog_test.go b/internal/cli/schema_catalog_test.go index 0f1bb3e0..0f5d4aad 100644 --- a/internal/cli/schema_catalog_test.go +++ b/internal/cli/schema_catalog_test.go @@ -1416,57 +1416,3 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) { } } } - -// TestDeliveryCatalogDingSemanticParamDeclsSpotCheck locks the ding pilot -// ParamDecl backfill: Execute/CallMCP keys win over flag-name inference, and -// required stays declare/cobra-backed rather than usage_required_inference. -func TestDeliveryCatalogDingSemanticParamDeclsSpotCheck(t *testing.T) { - cases := []struct { - path string - flag string - property string - required bool - }{ - {"ding +list", "cursor", "cursor", false}, - {"ding +list", "type", "type", false}, - {"ding +receiver-status", "ding-id", "openDingId", true}, - {"ding +recall-personal", "id", "openDingId", true}, - {"ding +send-personal", "users", "receiverOpenDingTalkIds", true}, - {"ding +send-personal", "content", "content", true}, - {"ding +send-personal", "type", "remindType", false}, - {"ding +send-personal", "uuid", "uuid", false}, - {"ding message send", "content", "content", true}, - {"ding message send", "robot-code", "robotCode", true}, - {"ding message send", "type", "remindType", false}, - {"ding message send", "users", "receiverUserIdList", true}, - {"ding message recall", "id", "openDingId", true}, - {"ding message recall", "robot-code", "robotCode", true}, - } - for _, tc := range cases { - leaf, err := queryDeliverySchemaPayload([]string{tc.path}) - if err != nil { - t.Fatalf("%s: %v", tc.path, err) - } - param := schemaMap(leaf["parameters"])[tc.flag] - if param == nil { - t.Fatalf("%s missing --%s", tc.path, tc.flag) - } - if got, _ := param["property"].(string); got != tc.property { - t.Fatalf("%s --%s property = %q, want %q", tc.path, tc.flag, got, tc.property) - } - propProv := schemaMap(param["field_provenance"])["property"] - if src, _ := propProv["source"].(string); src == "flag_name_inference" { - t.Fatalf("%s --%s property still flag_name_inference", tc.path, tc.flag) - } - gotRequired, _ := param["required"].(bool) - if gotRequired != tc.required { - t.Fatalf("%s --%s required = %v, want %v", tc.path, tc.flag, gotRequired, tc.required) - } - if tc.required { - reqProv := schemaMap(param["field_provenance"])["required"] - if src, _ := reqProv["source"].(string); src == "usage_required_inference" { - t.Fatalf("%s --%s required still usage_required_inference", tc.path, tc.flag) - } - } - } -} diff --git a/internal/helpers/ding.go b/internal/helpers/ding.go index 5506a59a..79cda622 100644 --- a/internal/helpers/ding.go +++ b/internal/helpers/ding.go @@ -115,8 +115,8 @@ func newDingCommand() *cobra.Command { }, }, Parameters: []contract.ParamDecl{ - {Name: "content", Property: "content", Required: boolPtr(true)}, - {Name: "robot-code", Property: "robotCode", Required: boolPtr(true)}, + {Name: "content", Required: boolPtr(true)}, + {Name: "robot-code", Required: boolPtr(true)}, {Name: "type", Property: "remindType"}, {Name: "users", Property: "receiverUserIdList", Required: boolPtr(true), InterfaceType: "array"}, }, @@ -170,10 +170,7 @@ func newDingCommand() *cobra.Command { Examples: []string{"dws ding message recall --robot-code --id --format json"}, }, Parameters: []contract.ParamDecl{ - // id is validateRequiredFlags-hard; robot-code is required unless - // DINGTALK_DING_ROBOT_CODE is set (same Agent-facing contract as send). - {Name: "id", Property: "openDingId", Required: boolPtr(true)}, - {Name: "robot-code", Property: "robotCode", Required: boolPtr(true)}, + {Name: "id", Property: "openDingId"}, }, }, }) diff --git a/internal/shortcut/ding/ding.go b/internal/shortcut/ding/ding.go index 6329cb85..e5e3bb2b 100644 --- a/internal/shortcut/ding/ding.go +++ b/internal/shortcut/ding/ding.go @@ -49,12 +49,6 @@ var List = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws ding +list"}, }, - // Property matches CallMCP keys in Execute; type stays optional because - // the CLI default ALL satisfies the server non-empty requirement. - Parameters: []contract.ParamDecl{ - {Name: "cursor", Property: "cursor"}, - {Name: "type", Property: "type"}, - }, }, Flags: []shortcut.Flag{ {Name: "cursor", Type: shortcut.FlagInt, Desc: "分页游标 (可选)"}, @@ -101,9 +95,6 @@ var ReceiverStatus = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws ding +receiver-status --ding-id "}, }, - Parameters: []contract.ParamDecl{ - {Name: "ding-id", Property: "openDingId", Required: boolPtr(true)}, - }, }, Flags: []shortcut.Flag{ {Name: "ding-id", Type: shortcut.FlagString, Desc: "openDingId", Required: true}, @@ -142,13 +133,6 @@ var SendPersonal = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws ding +send-personal --users --content "}, }, - // Property keys match send_personal_ding toolArgs (not flag-name camelCase). - Parameters: []contract.ParamDecl{ - {Name: "users", Property: "receiverOpenDingTalkIds", Required: boolPtr(true)}, - {Name: "content", Property: "content", Required: boolPtr(true)}, - {Name: "type", Property: "remindType"}, - {Name: "uuid", Property: "uuid"}, - }, }, Flags: []shortcut.Flag{ {Name: "users", Type: shortcut.FlagStringSlice, Desc: "接收人 openDingTalkId 列表 (CSV)", Required: true}, @@ -220,9 +204,6 @@ var RecallPersonal = shortcut.Shortcut{ AvoidWhen: []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"}, Examples: []string{"dws ding +recall-personal --id "}, }, - Parameters: []contract.ParamDecl{ - {Name: "id", Property: "openDingId", Required: boolPtr(true)}, - }, }, Flags: []shortcut.Flag{ {Name: "id", Type: shortcut.FlagString, Desc: "openDingId", Required: true}, @@ -234,8 +215,6 @@ var RecallPersonal = shortcut.Shortcut{ }, } -func boolPtr(v bool) *bool { return &v } - func init() { shortcut.Register( List, diff --git a/scripts/policy/schema-compat/main.go b/scripts/policy/schema-compat/main.go index 3be16b5d..84447246 100644 --- a/scripts/policy/schema-compat/main.go +++ b/scripts/policy/schema-compat/main.go @@ -730,6 +730,7 @@ func checkParameterCompatibility(toolPath, name string, oldParameter, newParamet new string }{ {name: "type", old: oldParameter.Type, new: newParameter.Type}, + {name: "property", old: oldParameter.Property, new: newParameter.Property}, {name: "default", old: oldParameter.Default, new: newParameter.Default}, {name: "interface_default", old: oldParameter.InterfaceDefault, new: newParameter.InterfaceDefault}, {name: "format", old: oldParameter.Format, new: newParameter.Format}, @@ -738,13 +739,6 @@ func checkParameterCompatibility(toolPath, name string, oldParameter, newParamet failures = append(failures, fmt.Sprintf("schema tool %q parameter %q changed %s", toolPath, name, field.name)) } } - // Property remaps are incompatible unless a reviewed correction replaces - // flag-name inference with the ParamDecl-declared MCP field. Arbitrary - // non-empty A→B remaps remain a contract break. - if oldParameter.Property != newParameter.Property && - !compatibleReviewedPropertyCorrection(toolPath, name, oldParameter.Property, newParameter.Property) { - failures = append(failures, fmt.Sprintf("schema tool %q parameter %q changed property", toolPath, name)) - } // Clearing interface_type is accepted as compatible: a deliberate, // wire-visible policy decision taken with the pinned MCP metadata // retirement. Production no longer projects MCP-sourced types unless @@ -789,61 +783,6 @@ func enumNarrowed(oldValues, newValues []string) bool { return false } -// reviewedPropertyCorrection is an exact allowlist entry for replacing a -// historical wire property (typically flag-name inference) with the MCP field -// declared on leaf ParamDecl.Property. Do not use this to bless arbitrary -// remaps: each entry must name tool path, parameter, old property, and new -// property exactly. -type reviewedPropertyCorrection struct { - toolPath string - param string - oldProperty string - newProperty string -} - -// Keep this list tiny and evidence-backed. Prefer ParamDecl declaration; only -// add an entry when schema-compat would otherwise freeze a wrong inferred -// property that agents bind against. ding pilot: Execute/CallMCP keys are the -// ground truth (see internal/shortcut/ding/ding.go). -var reviewedPropertyCorrections = []reviewedPropertyCorrection{ - { - toolPath: "ding/ding.shortcut_receiver_status", - param: "ding-id", - oldProperty: "dingId", - newProperty: "openDingId", - }, - { - toolPath: "ding/ding.shortcut_recall_personal", - param: "id", - oldProperty: "id", - newProperty: "openDingId", - }, - { - toolPath: "ding/ding.shortcut_send_personal", - param: "users", - oldProperty: "users", - newProperty: "receiverOpenDingTalkIds", - }, - { - toolPath: "ding/ding.shortcut_send_personal", - param: "type", - oldProperty: "type", - newProperty: "remindType", - }, -} - -func compatibleReviewedPropertyCorrection(toolPath, param, oldProperty, newProperty string) bool { - for _, entry := range reviewedPropertyCorrections { - if entry.toolPath == toolPath && - entry.param == param && - entry.oldProperty == oldProperty && - entry.newProperty == newProperty { - return true - } - } - return false -} - func mergeContracts(historical, current schemaContract) (schemaContract, []string) { failures := checkCompatibility(historical, current) if len(failures) > 0 { diff --git a/scripts/policy/schema-compat/main_test.go b/scripts/policy/schema-compat/main_test.go index d7486362..8b33b029 100644 --- a/scripts/policy/schema-compat/main_test.go +++ b/scripts/policy/schema-compat/main_test.go @@ -595,129 +595,13 @@ func TestCrossPlatformCoverageSchemaCompatMCPRetirementAndConstraintExpansion(t t.Fatalf("non-empty interface_type change should fail: %v", failures) } - // Reviewed ding property remaps must pass; arbitrary remaps must fail. - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "users", "users", "receiverOpenDingTalkIds", - ) { - t.Fatal("reviewed ding +send-personal --users correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "type", "type", "remindType", - ) { - t.Fatal("reviewed ding +send-personal --type correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_receiver_status", "ding-id", "dingId", "openDingId", - ) { - t.Fatal("reviewed ding +receiver-status --ding-id correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_recall_personal", "id", "id", "openDingId", - ) { - t.Fatal("reviewed ding +recall-personal --id correction must be accepted") - } - if compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "users", "users", "userIds", - ) { - t.Fatal("non-reviewed new property must remain incompatible") - } - if compatibleReviewedPropertyCorrection( - "ding/ding.send_ding_message", "users", "users", "receiverUserIdList", - ) { - t.Fatal("unlisted tool/param remap must remain incompatible") - } + // Property remaps remain a hard contract break (no reviewed allowlist). propertyRemap := cloneContract(baseline) mutateParameter(&propertyRemap, func(parameter *parameterSchema) { parameter.Property = "unreviewedRemoteField" }) if failures := checkCompatibility(baseline, propertyRemap); !strings.Contains(strings.Join(failures, "\n"), "changed property") { - t.Fatalf("unreviewed property remap should fail: %v", failures) - } -} - -func TestCrossPlatformCoverageReviewedDingPropertyCorrections(t *testing.T) { - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "users", "users", "receiverOpenDingTalkIds", - ) { - t.Fatal("reviewed ding +send-personal --users correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "type", "type", "remindType", - ) { - t.Fatal("reviewed ding +send-personal --type correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_receiver_status", "ding-id", "dingId", "openDingId", - ) { - t.Fatal("reviewed ding +receiver-status --ding-id correction must be accepted") - } - if !compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_recall_personal", "id", "id", "openDingId", - ) { - t.Fatal("reviewed ding +recall-personal --id correction must be accepted") - } - if compatibleReviewedPropertyCorrection( - "ding/ding.shortcut_send_personal", "users", "users", "userIds", - ) { - t.Fatal("non-reviewed new property must remain incompatible") - } - if compatibleReviewedPropertyCorrection( - "ding/ding.send_ding_message", "users", "users", "receiverUserIdList", - ) { - t.Fatal("unlisted tool/param remap must remain incompatible") - } - - baseline := schemaContract{ - Products: map[string]productSchema{ - "ding": {Tools: map[string]toolSchema{ - "ding.shortcut_send_personal": { - PrimaryCLIPath: "ding +send-personal", - InterfaceMode: "composite", - Availability: "available", - Effect: "write", - Risk: "medium", - Confirmation: "user_required", - Idempotency: "unknown", - Parameters: map[string]parameterSchema{ - "users": {Type: "array", Property: "users"}, - "type": {Type: "string", Property: "type"}, - }, - }, - "ding.shortcut_receiver_status": { - PrimaryCLIPath: "ding +receiver-status", - InterfaceMode: "composite", - Availability: "available", - Effect: "read", - Risk: "low", - Confirmation: "not_required", - Idempotency: "idempotent", - Parameters: map[string]parameterSchema{ - "ding-id": {Type: "string", Property: "dingId", Required: true}, - }, - }, - }}, - }, - } - corrected := cloneContract(baseline) - corrected.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["users"] = parameterSchema{ - Type: "array", Property: "receiverOpenDingTalkIds", - } - corrected.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["type"] = parameterSchema{ - Type: "string", Property: "remindType", - } - corrected.Products["ding"].Tools["ding.shortcut_receiver_status"].Parameters["ding-id"] = parameterSchema{ - Type: "string", Property: "openDingId", Required: true, - } - if failures := checkCompatibility(baseline, corrected); len(failures) != 0 { - t.Fatalf("reviewed ding property corrections should pass: %v", failures) - } - - broken := cloneContract(baseline) - broken.Products["ding"].Tools["ding.shortcut_send_personal"].Parameters["users"] = parameterSchema{ - Type: "array", Property: "userIds", - } - if failures := checkCompatibility(baseline, broken); !strings.Contains(strings.Join(failures, "\n"), "changed property") { - t.Fatalf("unreviewed property remap should fail: %v", failures) + t.Fatalf("property remap should fail: %v", failures) } }