跳到主要内容

策略

策略

策略是 src/access_control/policy/+<name>.policy.ts,以文件名命名。它包含按集合的 grants、持有者可以发起的 automations、集合之外的 capabilities,以及 limits。团队与策略的对应关系在 src/access_control/team/+team.ts 中定义。

API 参考:policy

// src/access_control/policy/+helper.policy.ts (templates/on-demand-services, excerpt)
import { policy } from '@norbital-ai/bolt';

const SELF = { user: { eq: { actor: 'id' } } } as const;
const OWN = { helper: { is: SELF } } as const;

/**
 * A helper: their own visits and the customer details needed to reach them, their own record, and the position their
 * app reports. They answer the shift check and move a visit through its day; everything else is the scheduler's.
 */
export default policy({
  description: 'A helper’s own visits, shift check answers, completion and reported position.',
  capabilities: { apps: ['helper'] },
  grants: {
    helpers: {
      read: { where: SELF, fields: ['id', 'user', 'name', 'phone', 'skills', /* … */ 'last_location_at'] },
      update: { where: SELF, fields: ['last_location'] }
    },
    helper_time_off: { read: { where: OWN } },
    services: { read: { fields: ['id', 'name', 'duration_minutes', 'description'] } },
    visits: {
      read: { where: OWN, fields: ['id', 'number', 'booking', 'helper', 'slot', /* … */ 'completion_notes'] },
      update: { where: OWN, fields: ['status', 'shift_check', 'mc', 'completion_notes'] },
      moves: { status: ['scheduled->in_progress', 'in_progress->done'] },
      actions: ['confirm_shift', 'decline_shift']
    },
    customers: {
      read: {
        where: { bookings: { some: { visits: { some: OWN } } } },
        fields: ['id', 'name', 'email', 'phone', 'address']
      }
    }
    // …
  },
  limits: { act: '120/min', read: '600/min', agent: '50/h' }
});

授权

授权指定一个集合及其各项:read、history、create、update、delete、queries、actions 与 moves(持有者可以执行的状态转移,或 'all')。每一项可以是 true、一个 Where 范围,或 { where, fields },其中 fields 缩小读取可见或写入可设置的字段。{ via: '<relation>' } 继承父记录的授权。

Where 可以使用 { actor: 'id' }、{ actor: 'teams' }、{ today: '' } 或 { startOf: 'month' } 等操作数,并用 is、some、none 与 every 跨越关系。在更新中,where 同时作用于写入前后的行;有 previous 时,where 作用于写入结果,previous 作用于已存储的行。

// src/access_control/policy/+suspicion_review_automation.policy.ts (templates/field-operations, excerpt)
const UNCHECKED = { suspicion_checked_at: { isNull: true } } as const;

grants: {
  job_assignments: {
    read: true,
    update: {
      previous: UNCHECKED,                                  // the stored row before the write
      where: { suspicion_checked_at: { isNull: false } },   // the row as it will be
      fields: ['suspicion_checked_at']
    }
  },
  photo_evidence: {
    read: true,
    update: {
      // a photo awaiting inspection, or a hashed one (seeded, bank-loaded) still without a scene
      previous: { or: [{ sha256: { eq: '' } }, { scene_embedding: { isNull: true } }] },
      fields: [
        'sha256',
        'inspection_failed_at',
        'inspection_failure_reason',
        'perceptual_embedding',
        'scene_embedding',
        'flags',
        'matched_evidence_ids'
      ]
    }
  }
}

这就是只授予一次状态转换的方法:上面的审查只能为尚未标记的任务写入 suspicion_checked_at,并且只能在照片哈希为空或尚无场景时填写照片的事实信息。

并集与遮罩

成员持有其所有策略的并集,对每一行由限制最少的授权决定是否允许。读取授权未列出的字段会被遮罩:不会出现在行、筛选、排序、标签与下载中。

能力与自动化

capabilities.apps 打开应用(指定分组名即可打开其下的全部应用),tools、mcp 与 skills 扩展 Agent。automations 列出持有者可以发起的自动化——以及 <collection>.integration 运行。

限流

limits 限制 act、read、agent、upload、register、单个查询或操作('<c>.<name>')的速率,对 Envoy 还有 envoys.receive 与 envoys.unrecognised。速率写作 '600/min' 或 '1/15min',默认按执行者计数,在公开应用上按 IP 计数,在 Envoy 的键上按发送者或按 Envoy 计数。

团队

// src/access_control/team/+team.ts (templates/on-demand-services)
import { team } from '@norbital-ai/bolt';

/** Source access roles: stable handles are membership and approval identities. */
export default team({
  "team-operations": {
    "name": "Operations",
    "parent": null,
    "policies": [
      "operations"
    ]
  },
  "team-helpers": {
    "name": "Helpers",
    "parent": null,
    "policies": [
      "helper"
    ]
  }
});

+team.ts 把团队句柄映射到各团队持有的策略;成员的来源团队是其行中的一列,指向一个句柄。TeamName 来自这个文件,因此审批步骤中拼错的审批团队是类型错误。团队有上级:团队树用于限定数据范围({ actor: 'teamTree' }),从不向下传递策略。