diff --git a/internal/helpers/drive.go b/internal/helpers/drive.go index ea48c463..46806c2c 100644 --- a/internal/helpers/drive.go +++ b/internal/helpers/drive.go @@ -2404,7 +2404,7 @@ func newDriveCommand() *cobra.Command { }, "policies":{ "type":"array", - "description":"仅包含支持的策略项,未下发或不受支持的策略不会返回;node_spread_scope 仅文件夹类节点返回", + "description":"仅包含支持的策略项,未下发或不受支持的策略不会返回;node_spread_scope 仅文件夹类节点返回;allowedValues 为当前可设置的取值,disabledValues 为当前不可设置的取值及原因,两者互斥", "items":{ "type":"object", "description":"权限策略项", @@ -2413,10 +2413,22 @@ func newDriveCommand() *cobra.Command { "name":{"type":"string","description":"策略的中文名称,文案与产品权限设置页一致;为确定性字段,只要该策略返回就必带"}, "description":{"type":"string","description":"策略的含义说明,解释该策略管控的行为及各取值的语义;为确定性字段,只要该策略返回就必带"}, "value":{"type":["string","null"],"description":"取值随策略类型不同:开关型(external_share、external_share_manager_only、member_invite_org_only、permission_apply、external_permission_apply、watermark、node_move_forbidden)为 ENABLED/DISABLED;阈值型(member_invite、comment)为 READER_AND_ABOVE/DOWNLOADER_AND_ABOVE/EDITOR_AND_ABOVE/MANAGER_AND_ABOVE,阈值型(node_spread、online_content_copy)为 DOWNLOADER_AND_ABOVE/EDITOR_AND_ABOVE/MANAGER_AND_ABOVE/NOBODY,均表示不低于该角色才允许对应操作,NOBODY 表示所有人禁止;二值型(node_spread_scope):ALL_NODES=下载与传播限制对所有文档生效,PREVIEWABLE_ONLY=仅对可预览的文档(在线文档、图片视频等)生效;未知值时为 null"}, - "inherited":{"type":["boolean","null"],"description":"true 表示该策略值继承自上级(父节点/知识库)配置;未下发时为 null"}, + "disabledValues":{ + "type":"array", + "description":"该策略当前不可设置的取值及禁用原因(与 allowedValues 互斥);为确定性字段,恒返回,无被禁取值时为空数组", + "items":{ + "type":"object", + "properties":{ + "value":{"type":"string","description":"被禁档位的取值(与 value 同一值域)"}, + "reason":{"type":["string","null"],"description":"服务端按请求语言返回的禁用原因文案,仅供展示理解,可为 null"} + }, + "required":["value"], + "additionalProperties":true + } + }, "allowedValues":{"type":["array","null"],"items":{"type":"string"},"description":"该策略当前可设置的取值(与 value 同一值域),未下发时为 null"} }, - "required":["code","name","description"], + "required":["code","name","description","disabledValues"], "additionalProperties":true } } diff --git a/internal/helpers/drive_permission_get_setting_test.go b/internal/helpers/drive_permission_get_setting_test.go index 49f91fa3..8d142bd0 100644 --- a/internal/helpers/drive_permission_get_setting_test.go +++ b/internal/helpers/drive_permission_get_setting_test.go @@ -131,16 +131,18 @@ func TestCrossPlatformCoverageDrivePermissionGetSettingResultContract(t *testing t.Fatalf("policies = %#v, want array schema", properties["policies"]) } policiesDescription, _ := policies["description"].(string) - if !strings.Contains(policiesDescription, "未下发或不受支持的策略不会返回") || !strings.Contains(policiesDescription, "node_spread_scope 仅文件夹类节点返回") { - t.Fatalf("policies description = %q", policiesDescription) + for _, fragment := range []string{"未下发或不受支持的策略不会返回", "node_spread_scope 仅文件夹类节点返回", "两者互斥"} { + if !strings.Contains(policiesDescription, fragment) { + t.Fatalf("policies description missing %q: %s", fragment, policiesDescription) + } } items, ok := policies["items"].(map[string]any) if !ok { t.Fatalf("policies items = %#v", policies["items"]) } - assertSchemaRequired(t, items, "code", "name", "description") + assertSchemaRequired(t, items, "code", "name", "description", "disabledValues") itemProperties := schemaProperties(t, items) - if got := sortedContractSchemaKeys(itemProperties); !reflect.DeepEqual(got, []string{"allowedValues", "code", "description", "inherited", "name", "value"}) { + if got := sortedContractSchemaKeys(itemProperties); !reflect.DeepEqual(got, []string{"allowedValues", "code", "description", "disabledValues", "name", "value"}) { t.Fatalf("policy item properties = %#v", got) } code, ok := itemProperties["code"].(map[string]any) @@ -174,12 +176,34 @@ func TestCrossPlatformCoverageDrivePermissionGetSettingResultContract(t *testing t.Fatalf("policy item %s description = %q", field, entryDescription) } } - inherited, ok := itemProperties["inherited"].(map[string]any) - if !ok { - t.Fatalf("inherited = %#v", itemProperties["inherited"]) + disabledValues, ok := itemProperties["disabledValues"].(map[string]any) + if !ok || disabledValues["type"] != "array" { + t.Fatalf("disabledValues = %#v, want array schema", itemProperties["disabledValues"]) } - if inheritedDescription, _ := inherited["description"].(string); !strings.Contains(inheritedDescription, "继承自上级") { - t.Fatalf("inherited description = %q", inherited["description"]) + disabledValuesDescription, _ := disabledValues["description"].(string) + for _, fragment := range []string{"与 allowedValues 互斥", "恒返回", "空数组"} { + if !strings.Contains(disabledValuesDescription, fragment) { + t.Fatalf("disabledValues description missing %q: %s", fragment, disabledValuesDescription) + } + } + disabledItems, ok := disabledValues["items"].(map[string]any) + if !ok { + t.Fatalf("disabledValues items = %#v", disabledValues["items"]) + } + assertSchemaRequired(t, disabledItems, "value") + disabledItemProperties := schemaProperties(t, disabledItems) + if got := sortedContractSchemaKeys(disabledItemProperties); !reflect.DeepEqual(got, []string{"reason", "value"}) { + t.Fatalf("disabledValues item properties = %#v", got) + } + if disabledValue, ok := disabledItemProperties["value"].(map[string]any); !ok || disabledValue["type"] != "string" { + t.Fatalf("disabledValues value = %#v, want string schema", disabledItemProperties["value"]) + } + reason, ok := disabledItemProperties["reason"].(map[string]any) + if !ok { + t.Fatalf("disabledValues reason = %#v", disabledItemProperties["reason"]) + } + if types, ok := reason["type"].([]any); !ok || len(types) != 2 || types[0] != "string" || types[1] != "null" { + t.Fatalf("disabledValues reason type = %#v, want [string null]", reason["type"]) } allowedValues, ok := itemProperties["allowedValues"].(map[string]any) if !ok { diff --git a/skills/mono/references/products/drive.md b/skills/mono/references/products/drive.md index 606aa7e5..369ddfbd 100644 --- a/skills/mono/references/products/drive.md +++ b/skills/mono/references/products/drive.md @@ -761,7 +761,8 @@ Flags: get-setting 返回字段说明: - `permissionMode` — INHERITED(继承上级)/ INDEPENDENT(独立管理),未知时为 null - `shareScope` — `visibility`(PRIVATE/ORGANIZATION/PUBLIC);`partnerIncluded`、`defaultRole`、`canSearch`、`canRecommend` 仅 ORGANIZATION 有意义;`linkShare`:`requirePassword`(密码明文不返回)、`expireAt`/`expireDays`(未设置为 null)、`forCurrentNode` -- `policies[]` — 每项含 `code`(策略码)、`name`/`description`(中文名与值语义说明,随行必带)、`value`(当前值)、`inherited`(true=继承上级配置)、`allowedValues`(可设置值域);未下发或不支持的策略不返回;`node_spread_scope` 仅文件夹类节点返回 +- `policies[]` — 每项含 `code`(策略码)、`name`/`description`(中文名与值语义说明,随行必带)、`value`(当前值)、`disabledValues`(不可设置取值列表)、`allowedValues`(可设置值域,与 disabledValues 互斥);未下发或不支持的策略不返回;`node_spread_scope` 仅文件夹类节点返回 +- `disabledValues[]` — 每项含 `value`(被禁档位取值,与 value 同一值域)与 `reason`(服务端按请求语言返回的禁用原因文案,仅供展示理解,可为 null);恒返回,无被禁档位时为空数组;示例:`{"value": "READER_AND_ABOVE", "reason": "企业安全策略要求不可低于可下载角色"}` - `value` 按策略分型:开关型(external_share、external_share_manager_only、member_invite_org_only、permission_apply、external_permission_apply、watermark、node_move_forbidden)为 ENABLED/DISABLED;member_invite、comment 为 READER/DOWNLOADER/EDITOR/MANAGER_AND_ABOVE(无 NOBODY);node_spread、online_content_copy 为 DOWNLOADER/EDITOR/MANAGER_AND_ABOVE 或 NOBODY(无 READER_AND_ABOVE);node_spread_scope 为 ALL_NODES(限制对所有文档生效)/ PREVIEWABLE_ONLY(仅对可预览的文档生效) - `name`/`description` 示例(文案与产品权限设置页一致):external_share「添加企业外协作者」:是否允许添加企业外的人为协作者(ENABLED=允许,DISABLED=禁止);node_spread「谁可以下载、创建副本、打印」:允许哪些角色及以上的用户下载、创建副本、打印;NOBODY=所有人禁止下载、创建副本、打印;node_move_forbidden「禁止移动」:是否禁止移动到其他知识库或团队共享文件夹(ENABLED=禁止移动,DISABLED=允许移动) - 方向语义:NOBODY=该操作对所有人禁止;XXX_AND_ABOVE=不低于该角色才允许 diff --git a/skills/multi/dingtalk-drive/references/drive.md b/skills/multi/dingtalk-drive/references/drive.md index 179464a2..e22d6515 100644 --- a/skills/multi/dingtalk-drive/references/drive.md +++ b/skills/multi/dingtalk-drive/references/drive.md @@ -80,7 +80,7 @@ dws drive +publish-unset --node 只在意图命中时读取一个精确 leaf Schema。成员变更、转移所有者、发起申请和公开状态变更必须明确节点、用户、角色与影响范围。转移所有者时,在构造最终命令前必须让用户分别明确决定 `--reserve-role ` 和 `--recursive=`;Agent 不得根据默认值、对象类型或便捷性自行选择任一项。两项决策与目标、新所有者均明确后,才按 Runtime confirmation 构造首次正式调用。 -`permission get-setting` 返回 `permissionMode`(INHERITED/INDEPENDENT,未知时为 null)、`shareScope`(可见范围与链接分享,密码明文不返回;`partnerIncluded`、`defaultRole` 等仅 ORGANIZATION 有意义)和 `policies[]`(code/name/description/value/inherited/allowedValues;name/description 为中文名与值语义说明,随行必带;未下发的策略不返回,`node_spread_scope` 仅文件夹)。`value` 按策略分型:开关型为 ENABLED/DISABLED;member_invite、comment 为 READER/DOWNLOADER/EDITOR/MANAGER_AND_ABOVE;node_spread、online_content_copy 为 DOWNLOADER/EDITOR/MANAGER_AND_ABOVE 或 NOBODY;node_spread_scope 为 ALL_NODES(限制对所有文档生效)/ PREVIEWABLE_ONLY(仅对可预览的文档生效)。NOBODY=该操作对所有人禁止;XXX_AND_ABOVE=不低于该角色才允许。name/description 示例(文案与产品权限设置页一致):external_share「添加企业外协作者」:是否允许添加企业外的人为协作者(ENABLED=允许,DISABLED=禁止);node_spread「谁可以下载、创建副本、打印」:允许哪些角色及以上的用户下载、创建副本、打印;NOBODY=所有人禁止下载、创建副本、打印;node_move_forbidden「禁止移动」:是否禁止移动到其他知识库或团队共享文件夹(ENABLED=禁止移动,DISABLED=允许移动)。 +`permission get-setting` 返回 `permissionMode`(INHERITED/INDEPENDENT,未知时为 null)、`shareScope`(可见范围与链接分享,密码明文不返回;`partnerIncluded`、`defaultRole` 等仅 ORGANIZATION 有意义)和 `policies[]`(code/name/description/value/disabledValues/allowedValues;name/description 为中文名与值语义说明,随行必带;未下发的策略不返回,`node_spread_scope` 仅文件夹)。`disabledValues` 为不可设置取值列表(恒返回,无被禁档位时为空数组),每项含 `value`(被禁档位取值,与 value 同一值域)与 `reason`(服务端按请求语言返回的禁用原因文案,仅供展示理解,可为 null),与 allowedValues 互斥;示例:`{"value": "READER_AND_ABOVE", "reason": "企业安全策略要求不可低于可下载角色"}`。`value` 按策略分型:开关型为 ENABLED/DISABLED;member_invite、comment 为 READER/DOWNLOADER/EDITOR/MANAGER_AND_ABOVE;node_spread、online_content_copy 为 DOWNLOADER/EDITOR/MANAGER_AND_ABOVE 或 NOBODY;node_spread_scope 为 ALL_NODES(限制对所有文档生效)/ PREVIEWABLE_ONLY(仅对可预览的文档生效)。NOBODY=该操作对所有人禁止;XXX_AND_ABOVE=不低于该角色才允许。name/description 示例(文案与产品权限设置页一致):external_share「添加企业外协作者」:是否允许添加企业外的人为协作者(ENABLED=允许,DISABLED=禁止);node_spread「谁可以下载、创建副本、打印」:允许哪些角色及以上的用户下载、创建副本、打印;NOBODY=所有人禁止下载、创建副本、打印;node_move_forbidden「禁止移动」:是否禁止移动到其他知识库或团队共享文件夹(ENABLED=禁止移动,DISABLED=允许移动)。 发起权限申请先只读执行 `permission apply-info`。正式 `permission apply` 会通知审批人;调用前必须向用户逐项回显并确认资源、申请角色、审批人和理由。Agent 不得默认选择第一位审批人、最高/最低角色或代写申请理由;用户未明确同意完整申请内容时停在确认环节。