Revert "declare(ding): semantic ParamDecl backfill for schema inference residuals"

This reverts commit 969292a8d7.
This commit is contained in:
玉澜 committed 2026-08-06 14:52:34 +08:00
1 parent a1f8ecb7f0
commit 0353215b1d
5 files changed
+6 -261

No files matched your search

-54
View File
@@ -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)
}
}
}
}
+3 -6
View File
@@ -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 <ROBOT_CODE> --id <OPEN_DING_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"},
},
},
})
-21
View File
@@ -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 <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 <VALUES> --content <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 <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,
+1 -62
View File
@@ -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 {
+2 -118
View File
@@ -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)
}
}