第六支柱 · 访问控制(阀门)
父级:key-designs
谁能看到什么、能做什么——这是测试最重的一根支柱(acl-* 端到端测试矩阵)。入口通过访问码(access code,即邀请码、二维码);访问码针对某个角色签发,该角色的 RoleSnapshot 在签发时冻结(会话内规则不再漂移)。授权由三层的纯 AND 收窄组合而成——global(实时)∧ role(冻结)∧ ¬code-deny(冻结)——访问码只能做减法,不能重新放行(稀疏拒绝表,没有三态)。身份走中立的 _meta 侧信道;owner 身份是一个 Ed25519 密钥对(私钥永不进入 domain 层);BYOAI 模式允许访客在一个信封(envelope)约束下带自己的模型进来。
状态: 已落地(全局层已上线;角色/访问码拒绝表按 2026-06-23 定稿的设计实现)。
flowchart TB
ALL["all capabilities + skills"] --> G{"global enabled?
(live — flips kill sessions)"}
G --> R{"role grants?
(frozen at issue → RoleSnapshot)"}
R --> C{"code denies?
(sparse deny tables — subtract only)"}
C --> DQ{"connector deps met ∧ quota ok"}
DQ --> EXP["exposed set for this session"]
G -. "deny" .-> X1["hidden"]
R -. "not granted" .-> X1
C -. "denied" .-> X1
数据模型(ER)
erDiagram
OWNERS ||--o{ ROLES : defines
ROLES }o--o| PROMPTS : "prompt_id (persona)"
ROLES ||--o{ ROLE_SKILLS : grants
ROLES ||--o{ ROLE_MCP_SERVERS : grants
ROLES ||--o{ ROLE_CORPUS_URIS : scopes
ACCESS_CODES }o--|| ROLES : "assumed_role_id"
ACCESS_CODES ||--o{ CODE_CAPABILITY_DENIALS : "deny (sparse)"
ACCESS_CODES ||--o{ CODE_SKILL_DENIALS : "deny (sparse)"
ACCESS_CODES ||--o{ CODE_CORPUS_DENIALS : "deny (sparse, uri_pattern)"
ACCESS_CODES ||--o{ CODE_MEMBERS : admits
ACCESS_CODES ||--o| EMBEDS : "exposed by (code_id UNIQUE)"
OWNERS ||--o{ CAPABILITY_SETTINGS : "global layer (live)"
OWNERS ||--o{ OWNER_KEYPAIRS : "Ed25519"
结合三层来读这张图:capability_settings 是实时的全局层;从 roles 可达的一切都会在签发时冻结进 RoleSnapshot;code_*_denials 系列表(capability / skill / corpus——corpus 这张 2026-07-16 落地,6395374b0,backend/db/schema.sql 的 code_corpus_denials)是冻结的、只做减法的第三层。
时序故事——每一层控制在何时起作用
sequenceDiagram participant O as Owner participant B as Backend participant V as Visitor O->>B: create role + issue access code Note over B: RoleSnapshot FROZEN at issue<br/>(role grants + code denials) V->>B: open /handle?code=... B->>B: load frozen snapshot B->>B: assemble capabilities through gates<br/>(undiscovered = ErrHidden) Note over B,V: ACL acts at SESSION ESTABLISHMENT —<br/>denied things are never discovered V->>B: chat turns... Note over B: call-time inline = monitor + secret-scan ONLY O->>B: flip capability_settings (global) Note over B: LIVE layer — running sessions killed instantly
子节点
- access-control-diagram-coverage — 图示覆盖台账。
- acl-and-quota-granularity — 三层纯 AND 模型;稀疏拒绝表。
- role-snapshot-frozen — 签发时冻结 vs 实时的全局层。
- trusted-identity-via-meta — 通过中立的
_meta侧信道传递身份。 - byoai-envelope · byoai-browser-vault — 访客自带模型,被约束在信封内。
- owner-keypair-auth —
/mcp/*上的 Ed25519 Sigv1:私钥 PEM 只返回一次,±5 分钟窗口 + 一次性 nonce;⚠ 签名绑定的是调用方,不是请求本身。 - embed-credential-never-carries-the-code — 上一条的 embed 版:每-embed 的 Ed25519 EdDSA JWT 鉴权 widget 会话,让 code 留在服务端;折进绑定 origin +
exp+ 一次性jti。已实现 2026-09-01(2098975db,backend/internal/access/usecase/embed_token.go);Turnstilecnf绑定是唯一仍延后的一层。 - owner-sessions-and-abuse-controls — 小阀门:退出真会撤销的 Redis owner 会话 + 活动会话面板、密钥对最后使用的设备/ip、按 IP 的邀请码失败锁定(
codefail:ip:,15 分钟内 10 次)与banned_ips、无码层的 gas 计量、每收件人每小时 30 封的邮件节流、APP_BIND_HOST+Referrer-Policy。已实现 2026-08-31 → 2026-09-06。 - coded-landing-and-code-rotation — 一张 code 的三个部分:行 id(关联的键)、snowflake slug →
/c/<slug>(定位符,永远不是凭据;?code=在首次绘制时就被吸收出 URL)、以及 64 位的 code 字符串——codes.rotate唯一替换的部分,杀掉每一份已打印副本和活会话,而 embed/application 存活。2026-09-06 → 07 上线(c6c54ce88、505b3fc4f、backend/internal/access/usecase/codes.go)。