策略
策略
策略是 src/access_control/policy/+<name>.policy.ts,以文件名命名。它包含按集合的 grants、持有者可以发起的 automations、集合之外的 capabilities,以及 limits。团队与策略的对应关系在 src/access_control/team/+team.ts 中定义。
// 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' }),从不向下传递策略。