Files
skill-template/references/POLICY.md
chendelian afe0788488
All checks were successful
技能自动化发布 / release (push) Successful in 7s
Release v1.0.120: document bind.visibleWhen for row menu visibility
2026-09-29 20:41:23 +08:00

14 KiB
Raw Permalink Blame History

Skill Data Policy 契约(v1.0)

状态:正式契约 v1.0(schemaVersion 1)。
目标: 冻结「启用方式 + Snapshot 最小字段 + L2 scope 白名单」,使多数技能(L0)零配置,需要权限的技能(L1/L2)有稳定扩展面。
非目标: 宿主实现业务用户/角色库;规定飞书/HTTP/本地表等权限数据源;本期不强制 L3(菜单级藏表、rows.mode=delegate 等)。
注意:

相关正交轴见 ACTIONS.md。本契约增加第六轴:policy(当前身份允许什么)。


0. 能力档位(必须按此理解)

档位 技能配置 宿主行为
L0 无 policy,或 policy.enabled !== true 与今日完全一致:不拉 Snapshot,不按身份过滤按钮/行/列
L1 policy.enabled: true,Snapshot 至少给出 Action 授权 数据管理展示 skill Action 前与 allowlist 求交;可选限制宿主内置能力
L2 在 L1 基础上 Snapshot 带 data.tables[table] 列表查询应用声明式 rows.scope;列按 columns 投影
L3 (预留,本契约不冻结行为) 如菜单级藏表、rows.mode=delegate、更复杂的行态策略字段等(≠ bind.visibleWhen 行菜单,≠ 表单 inputSchema.visibleWhen)

80% 技能停留 L0。 启用 policy 的技能必须实现约定 CLI,并在写路径自行再鉴权(见 §7)。


0.1 已冻结最佳实践(v1.0)

议题 决定 技能开发者怎么做
Skill Action 授权模式 仅 allowlist / open;v1 禁止 denylist 有角色就算出允许的 action id 列表;只控行不控按钮时用 open
hostCapabilities 省略 = 不限制宿主内置;写出则按 allowlist([] = 全禁内置) 只控业务按钮时不要写该字段;要限导出/删除时再显式列出
scope 运算符 保留小白名单(见 §3.4);禁止 like/raw/嵌套 日常优先 eq / in / is_null;比较符按需
valueFrom v1 仅 actor.userId 角色只在技能内用来算 Snapshot,不要用 actor.roles 做行过滤

1. 启用字段(冻在 assets/actions.json 顶层)

actions.json 根对象增加可选字段 policy(与 schemaVersion / skill / actions 同级)。

1.1 形状(L0/L1 必冻)

{
  "schemaVersion": 1,
  "skill": "your-skill-slug",
  "policy": {
    "enabled": true,
    "entrypoint": {
      "type": "cli",
      "command": "policy",
      "args": ["--table", "{{table}}"]
    },
    "appliesTo": ["data-management"]
  },
  "actions": []
}
字段 必填 冻结说明
policy 否 缺省 = L0
policy.enabled 若存在 policy 则必填 仅 true 启用;false 等同 L0
policy.entrypoint enabled: true 时必填 与 Action entrypoint 同构:type 目前仅 "cli"
policy.entrypoint.command 是 子命令名;推荐固定为 policy
policy.entrypoint.args 是 JSON 数组;可含占位符,由宿主替换后再拼接 CLI
policy.appliesTo 否 缺省 ["data-management"]。本期宿主只实现 data-management;其它值忽略但不报错

1.2 占位符白名单(冻)

占位符 含义 例
{{table}} 当前数据管理选中的物理表名 customs_timelines
{{skill}} 当前技能 slug 与 manifest skill 一致
{{userId}} 当前宿主用户 id 与注入的 JIANGCHANG_USER_ID 一致

未列出的 {{…}}:禁止;宿主应视为契约错误(fail-closed,见 §6)。

1.3 显式不放进启用块的内容(防推翻)

以下不得出现在 actions.json.policy 中(属于技能内部或 Snapshot):

  • 角色列表、人员表、飞书 app id、外部 IAM URL
  • 具体 allowlist / scope(那是 Snapshot 的事)
  • 按角色复制的 Action 定义

1.4 Action 级可选提示(不冻为运行时权威)

允许(文档/静态检查用),宿主不得仅凭此显隐按钮:

"authz": { "capability": "optional.skill.defined.string" }

运行时权威仅为 Snapshot §2 的 actions。


2. Snapshot 最小字段集(policy CLI 标准输出)

  • 编码:UTF-8 JSON 对象,写在 stdout(可先打日志到 stderr/文件,但 stdout 必须是单 JSON)。
  • 成功退出码 0;业务上「无权限看任何东西」仍是 0 + 合法 Snapshot(例如空 allowlist),不是靠非 0 表示未授权。
  • 非 0 或非合法 JSON → §6 fail-closed。

2.1 根对象(L0 不出现;L1 必冻)

字段 必填 类型 说明
schemaVersion 是 integer 本期冻结为 1
skill 是 string 必须等于 manifest skill
actor 是 object 见 §2.2
actions 是 object 见 §2.3
hostCapabilities 否 string[] 见 §2.4;省略 = 不限制宿主内置按钮(兼容)
data 否 object 见 §3;省略 = 不限制行/列(L1-only)
extensions 否 object 技能私有扩展;宿主必须忽略未知键

根级未知字段:宿主忽略;不得依赖未写入本契约的字段做安全决策。

2.2 actor(L1 必冻)

字段 必填 说明
userId 是 非空;应与 JIANGCHANG_USER_ID / {{userId}} 对齐
displayName 否 展示用
roles 否 string[],角色码;仅供展示 / 技能自检。不得用于 v1 scope 的 valueFrom
attributes 否 object,仅允许 JSON 标量值;本期 scope 不解引用

2.3 actions(L1 必冻)— Skill Action 授权

最佳实践: 角色 → 技能内计算 → allowlist 的 id 列表。宿主不理解角色名。

"actions": {
  "mode": "allowlist",
  "ids": ["whoami", "create-record", "register-submit"]
}
字段 必填 冻结取值
mode 是 仅两值:"allowlist" | "open"
ids mode=allowlist 时必填 string[];元素为 actions.json 里的 action.id
ids mode=open 时 必须省略或 [];宿主视为不限制 skill Action

规则:

  1. 宿主先按现有规则得到候选集:placements ∩ bind.tables(及 batch 等)。
  2. 若 mode=allowlist:候选 ∩ ids。漏配 id = 少按钮(安全侧失败)。
  3. ids 中的未知 id:忽略(不报错)。
  4. mode=open:等价「L1 不限制按钮」,可与 L2 行列限制并存。
  5. denylist 不是 v1 合法值;出现则 §6 fail-closed。

replaces 接管的默认新增/编辑/删除/导入:视为对应 Action 的 id 是否在 allowlist 中;不在则宿主隐藏该接管按钮(导入按钮是否可用还受 hostCapabilities 含 import 影响)。mode=open 时不按 id 限制 replaces。

2.4 hostCapabilities(L1 可选冻)

控制数据管理宿主内置能力,与 skill action.id 命名空间分离。

本期枚举冻结:refresh | export | view | edit | delete | import。

写法 含义 何时用
省略字段 不限制(与今日一致) 默认推荐:只控 skill 按钮 / 行数据时
写出非空数组 仅允许列出的内置能力(allowlist) 要限导出、删除等时
写出 [] 禁止全部上表内置能力 极少见;慎用

未知字符串:忽略。禁止把「省略」解释成全禁。


3. L2:data 与 scope 语法白名单

3.1 data 形状

"data": {
  "tables": {
    "customs_timelines": {
      "accessible": true,
      "columns": {
        "visible": ["id", "contract_no", "status"],
        "writable": ["status"]
      },
      "rows": {
        "mode": "scope",
        "scope": {
          "all": [
            { "field": "owner_user_id", "op": "eq", "valueFrom": "actor.userId" }
          ]
        }
      }
    }
  }
}
字段 必填 说明
data.tables 若存在 data 则建议有 key = 物理表名
accessible 否 缺省 true。false:宿主可拒绝打开该表
columns.visible / writable 否 物理列名;省略 = 不额外限制
rows 否 见下

writable 应 ⊆ visible;若违规,宿主按交集降级,不 fail-closed。

3.2 rows.mode(冻)

值 本期 含义
scope 实现目标 宿主将 scope 编译为安全查询约束
open 允许 显式不限制行
delegate 仅占位 本期未实现时 fail-closed / 提示;禁止当 open

缺省:无 rows = open。

3.3 scope 布尔结构(冻)

Scope =
  | { "all": Predicate[] }      // AND,长度 ≥ 1
  | { "any": Predicate[] }      // OR,长度 ≥ 1
  | Predicate                   // 单个谓词的语法糖

禁止:嵌套超过一层。

3.4 Predicate 白名单(冻)

运算符(宿主必须实现): eq | ne | in | not_in | gt | gte | lt | lte | is_null | is_not_null

推荐日常只用: eq / in / is_null(及 is_not_null)。

明确排除: like / contains / sql / raw / expr。

valueFrom v1 仅: actor.userId。
actor.roles / actor.attributes.* 非法 → fail-closed。

value 与 valueFrom 互斥。标量:string | number | boolean | null(与 null 比较优先用 is_null)。

3.5 与宿主原有过滤的关系

最终行集 = 原有表查询(搜索、排序、分页、用户 Filter) AND policy scope。宿主必须把 scope 下推到查询层。


4. 宿主消费算法

IF manifest.policy?.enabled !== true:
  → L0;END

调用 entrypoint(替换占位符),解析 Snapshot
IF 失败 / 非法 / skill 不匹配 / mode 非法:
  → fail-closed(§6);END

候选 = filter(placements, bind.tables, …)
IF mode == allowlist: 候选 ∩ ids
IF mode == open: 候选不变

渲染 skill 按钮 ← 候选
渲染宿主内置 ← hostCapabilities(省略则不限制)
应用 data.tables[T](若有)

拉取时机(建议):进入表、切换表、刷新。短缓存不得跨 userId。


5. 身份与环境

项 约定
用户主键 JIANGCHANG_USER_ID
技能解析 飞书 / 本地表 / HTTP IAM 等,契约不规定

6. Fail-closed(启用后必冻)

当且仅当 policy.enabled === true:

情况 宿主行为
CLI 非 0 / 超时 / 无 JSON 不渲染该表 skill 写操作与(建议)导出;明确错误;禁止回退 L0 全开
schemaVersion ≠ 1 / skill 不匹配 / actions.mode 非法 同上
rows.mode=scope 但 predicate 非法 不返回行或整表不可用;禁止忽略 scope
rows.mode=delegate(未实现) 提示不支持;禁止当 open

7. 安全边界

  1. Snapshot 只服务宿主 UI 与查询约束。
  2. 写 Action / 敏感读的 domain service 必须再次鉴权。
  3. 禁止「只藏按钮」宣称为完整权限方案。
  4. Cron / Agent 不自动套用本 Snapshot。

8. 最小示例

模板仓库另有可复制示例:

8.1 L0

不写 policy 字段。

8.2 L1 allowlist(不碰宿主内置)

{
  "schemaVersion": 1,
  "skill": "your-skill-slug",
  "actor": { "userId": "u1", "displayName": "张三", "roles": ["clerk"] },
  "actions": {
    "mode": "allowlist",
    "ids": ["whoami", "create-record", "register-submit"]
  }
}

8.3 L1 + 限制导出

{
  "schemaVersion": 1,
  "skill": "your-skill-slug",
  "actor": { "userId": "u1", "roles": ["clerk"] },
  "actions": { "mode": "allowlist", "ids": ["whoami"] },
  "hostCapabilities": ["refresh", "view"]
}

8.4 L1 open + L2 scope

{
  "schemaVersion": 1,
  "skill": "your-skill-slug",
  "actor": { "userId": "u1" },
  "actions": { "mode": "open" },
  "data": {
    "tables": {
      "orders": {
        "rows": {
          "mode": "scope",
          "scope": {
            "all": [
              { "field": "owner_user_id", "op": "eq", "valueFrom": "actor.userId" }
            ]
          }
        }
      }
    }
  }
}

9. 刻意延后

项 备注
actions.mode: denylist 另开 schemaVersion
valueFrom: actor.roles 明确不做
visibleWhen / 嵌套 scope / like L3 或永不做(此处指行态策略字段;Action 表单 inputSchema.visibleWhen 见 ACTIONS.md,不属本表)
rows.mode: delegate 宿主实现 字段名已占坑
平台全局 Role UI / cron·agent 套用 Snapshot 另议

10. 冻结检查清单

  • L0 缺省无歧义
  • 启用字段仅 enabled + entrypoint + 可选 appliesTo
  • actions.mode 仅 allowlist / open
  • hostCapabilities 省略 = 不限制
  • scope 小白名单;valueFrom 仅 actor.userId
  • fail-closed;写路径技能自检