# 组织侧 Workspace 访问管理规范

> Canonical path: `specs/organization/workspace-access-management.md`
> 状态：目标设计
> 相关规范：[AgentTeam 组织与权限规范](./organization.md)，[Invite Identity Target Spec](../identity/invite-identity.md)

本规范定义 organization 级角色如何管理 organization-owned workspace 的访问权，同时不被隐式加入这些 workspace 的消息空间。

---

## 1. 目的

AgentTeam 必须把两类边界分开：

- `organization` 是人员、team、策略、workspace ownership 与 access grant 的治理边界。
- `workspace` 是 message、channel、DM、thread、machine、agent 与 runtime 操作的协作边界。

organization owner/admin 必须能管理“谁可以进入 organization 拥有的某个 workspace”。但这种治理能力不能自动授予该 admin 读取 workspace 内容的权限。

稳定规则：

> 管理 organization-owned workspace access 是 organization governance action，不等价于参与该 workspace。

### 1.1 v1 Scope

v1 只解决一个收敛问题：

- `org_owner` / `org_admin` 可以在不加入 target workspace 的前提下，为已有 active `organization_member` 授予或撤销该 workspace 的 direct access。

v1 明确不做：

- 不新增业务表；
- 不引入新的通用 RBAC / policy DSL；
- 不把 organization admin 自动物化为所有 workspace member；
- 不新增 organization-scoped external invite 路由作为首批要求；
- 不改变 channel / DM / thread / private channel 的消息空间访问规则；
- 不把 team grant 授权委托给普通 workspace manager。

---

## 2. 问题

workspace-first 模型要求 organization membership 不能自动扩散为所有 workspace 的访问权，这是正确的。但 access-management 流程如果同时要求：

- active organization admin 权限；
- target workspace 内的 active workspace member-management 权限；

就会阻断一种合法场景：organization admin 负责治理组织和 workspace 访问权，但没有被加入某个具体 workspace。

另一个错误做法是自动把所有 organization admin 加入全部 workspace。这样会泄露消息空间访问权，污染 channel membership projection，意外消耗 seat，并让 private workspace 边界变得不可解释。

目标模型是第三条路径：

- organization admin 以治理元数据的形式管理 workspace access；
- 目标用户只有在获得 access 时才物化为 workspace member；
- 执行操作的 organization admin 不会因此被物化，除非他自己也是 access grant 的目标。

---

## 3. 设计决策

### 3.1 Governance 与 Participation 分离

organization workspace access management 是以下对象上的治理动作：

```text
organization_id + workspace_id + organization_member_id/team_id/invite_target
```

它不是 `workspace.access`，也不要求 actor 在 target workspace 中已有 `members` 行。

一个 actor 即使能通过 organization surface 管理 workspace access，也不会因此获得：

- channel、DM、thread、task、message 或 attachment 读取权限；
- workspace runtime control；
- machine management；
- agent operation；
- private channel discovery；
- target workspace 下的 `scope_memberships` 行。

### 3.2 统一物化路径

所有“把 human 加入 workspace”的产品入口必须收敛到同一条 service-layer materialization path。

该路径必须统一负责这些写入：

- invitation policy 明确允许时的 organization membership 校验或创建；
- direct member grant 或 team grant；
- workspace human `members` projection；
- `organization_workspace_member_projections` reconciliation；
- workspace `#all` channel 的 `scope_memberships` projection；
- invite accepted/revoked/dismissed 状态；
- 创建新 organization seat 的流程必须做 quota check。

调用方不能只创建 `members` 行，或只补 `scope_memberships` 行作为捷径。

### 3.3 保留数据

access change 是历史治理事件。普通产品路径必须保留数据，并使用可回溯的状态变化。

规范要求：

- grant 创建使用 insert 或 reactivation/upsert；
- grant 撤销设置 inactive/revoked 状态并触发 projection reconciliation；
- org-derived access 消失导致的 workspace member 移出，使用 `members.status = inactive`；
- scope 可见性移除使用 projection 的 removed/ended 状态，不硬删行；
- 已接受 invite 保留 `accepted_at`；
- 已撤销 invite 保留 `revoked_at`。

workspace access-management 产品路径不包含 hard delete。

### 3.4 v1 不新增表

v1 应复用现有 schema，不为该方案新增表。

现有表已经覆盖目标模型：

- `organization_workspace_member_grants`：direct member grant 的 desired state；
- `organization_workspace_team_grants`：team grant 的 desired state；
- `organization_workspace_member_projections`：org grant 到 workspace human member 的 materialized projection 与 baseline role 记录；
- `workspace_invites` / `workspace_invite_dismissals`：email/mobile workspace invite 与用户侧忽略状态；
- `organization_members`、`organization_member_team_memberships`、`organization_member_unit_memberships`：组织成员、team、unit 事实；
- `members`：workspace human member projection；
- `scope_memberships`：message-space scope visibility projection。

v1 允许新增 service method、authorization helper、API route、index 或约束校验，但不引入新的业务表。只有在这些现有表无法表达审计、幂等或性能需求，并且有明确查询路径与迁移方案时，才允许单独提出 schema 变更。

---

## 4. 授权模型

### 4.1 逻辑动作

本规范引入逻辑治理动作 `organization.manage_workspace_access`。

该动作在 organization-owned workspace 上判定：

```text
organization_id
workspace_id
actor_user_id
```

满足以下条件时允许：

- organization 处于 active；
- actor 在该 organization 下有 active `organization_members` 行；
- actor role 是 `org_owner` 或 `org_admin`；
- target workspace 通过 `workspaces.organization_id` 属于该 organization。

该动作不等价于 `workspace.manage_members`。

### 4.2 Workspace-local 管理

workspace owner/admin 的 workspace-local 管理能力仍然有效。

workspace-local action 继续通过 workspace authorization 判定：

- `workspace.manage_members` 可以通过 workspace surface 邀请或添加普通成员；
- 当操作会把 compatibility role 提升到普通 member 以上时，必须满足 `workspace.manage_member_roles`。

workspace-local 入口仍必须调用与 organization 入口相同的物化 service。

### 4.3 操作级 Authorizer

| 操作 | Organization owner/admin | Workspace manager | 说明 |
|------|--------------------------|-------------------|------|
| List organization workspaces | 允许 | 单独不够 | 产品允许 discovery 时，active organization member 可查看 catalog |
| List workspace access grants | 允许 | 对其 workspace 允许 | 返回治理元数据，不返回内容 |
| Grant direct access to organization member | 允许 | 对其 workspace 允许 | 目标必须是 owning organization 下的 active member |
| Revoke direct org-derived access | 允许 | 对其 workspace 允许 | 只 reconcile projection，不移出 organization |
| Grant team access | 允许 | 单独不够 | team grant 是 organization governance，可能影响多人；v1 不扩大给 workspace manager |
| Revoke team access | 允许 | 单独不够 | 仍需遵守 default workspace/default team invariant |
| Create workspace invite for external email/mobile | v1 不新增 org 路径 | 对其 workspace 允许 | invite target 必须遵守 identity 领域的严格 email/mobile resolver；v1 继续使用 workspace-local invite，或先通过 org invite/directory 加入 organization 后 direct grant |
| Grant `owner` compatibility role | 仅 `org_owner`，或 existing workspace role manager | workspace admin 单独不够 | 不允许产生无 owner workspace |

实现可以使用更窄的 helper 函数名，但用户可见语义必须符合上表。

### 4.4 Owner Role Guardrails

compatibility role `owner` 是高影响 workspace role。

普通 access-management 代码必须满足：

- `org_admin` 可以授予 `member` 或 `admin` workspace access；
- `org_owner` 可以授予 `owner` workspace access；
- existing workspace owner 可以在 workspace role-management 路径允许时修改 owner；
- 任何普通操作都不能让 active workspace 没有 effective owner；
- owner recovery 必须可审计。

---

## 5. API 契约

### 5.1 Organization-scoped Routes

organization-scoped workspace access routes 是治理路由，应归在 organization route family 下。

v1 使用现有 member/team grant route shape：

```text
GET    /api/v1/orgs/:orgId/workspaces
GET    /api/v1/orgs/:orgId/workspaces/:workspaceId/members
POST   /api/v1/orgs/:orgId/workspaces/:workspaceId/members/:organizationMemberId
DELETE /api/v1/orgs/:orgId/workspaces/:workspaceId/members/:organizationMemberId
GET    /api/v1/orgs/:orgId/workspaces/:workspaceId/teams
POST   /api/v1/orgs/:orgId/workspaces/:workspaceId/teams/:teamId
DELETE /api/v1/orgs/:orgId/workspaces/:workspaceId/teams/:teamId
```

现有 member/team grant routes 已经符合目标 route shape。v1 的主要实现工作是调整 direct member grant 的授权：按本规范允许 organization mode，而不是要求 actor 同时也是 target workspace member。

organization-scoped external invite routes 不进入 v1。若后续需要由 org admin 直接邀请 external email/mobile 到某个 workspace，应单独扩展路由，并复用本规范的 storage/projection 约束与 [`specs/identity/invite-identity.md`](../identity/invite-identity.md) 的 target 解析和接受规则。

### 5.2 Workspace-scoped Routes

workspace owners/admins 可以继续使用 workspace-scoped routes：

```text
POST   /api/v1/workspaces/:workspaceId/invites
GET    /api/v1/workspaces/:workspaceId/invites
POST   /api/v1/workspaces/:workspaceId/members
PATCH  /api/v1/workspaces/:workspaceId/members/:memberId
```

这些 routes 仍是 workspace-local surface，但必须委托给与 organization routes 相同的 domain command，保证 storage effect 一致。

### 5.3 Response Boundary

organization workspace access routes 可以返回：

- workspace id、slug、name、default flag、human/agent aggregate count；
- organization member id、user id、display identity、status、source、用于选人的 team/unit metadata；
- direct grant role、team grant role、source、projected workspace member id。

这些 routes 不得返回：

- channel message content；
- DM/thread content；
- private channel membership；
- task discussion content；
- attachment；
- machine runtime secret；
- agent execution private state。

打开 workspace 内容 UI 仍必须满足 `workspace.access`。

---

## 6. Storage And Projection Contract

### 6.1 Existing Organization Member Direct Grant

给 active organization member 授予 workspace access 时必须：

1. 校验 active organization settings 与 workspace ownership。
2. 校验目标 `organization_members` 行 active 且属于同一 organization。
3. Upsert `organization_workspace_member_grants`，字段至少包括：
   - `organization_id`
   - `workspace_id`
   - `organization_member_id`
   - `workspace_role`
   - `status = active`
   - `source = direct`
   - `granted_by_user_id`
4. 基于 direct 与 team grants 重新计算 effective access。
5. 创建或重新激活目标用户在 workspace 下的 human `members` 行。
6. 保留任何更高的 pre-existing baseline workspace role。
7. Upsert `organization_workspace_member_projections`。
8. 确保目标 member 拥有 workspace `#all` channel 的 `scope_memberships` projection。

执行操作的 organization admin 不会获得任何 `members` 或 `scope_memberships` 行，除非他自己就是 grant target。

### 6.2 Team Grant

给 team 授予 workspace access 时必须：

1. 校验 active organization settings 与 workspace ownership。
2. 校验 team 属于该 organization 且 active。
3. Upsert `organization_workspace_team_grants`。
4. Reconcile team 中当前所有 active organization members。
5. 未来 team membership 变化也必须应用同一套 effective-grant 逻辑。

对大型 synced team，可以异步 reconcile，但 durable desired state 必须先写入。

### 6.3 External Workspace Invite 后续扩展

v1 不要求新增 organization-scoped external workspace invite。邀请 external email/mobile 加入 organization-owned workspace 是后续扩展能力；它是 workspace access intent，不是完整 organization invite。email/mobile target 的解析、规范化、展示与接受校验必须遵守 [`Invite Identity Target Spec`](../identity/invite-identity.md)。

后续实现该能力时，invite path 必须：

1. 为严格解析后的 email/mobile target 创建或重新激活 pending `workspace_invites` 行。
2. 用 normalized form 存储目标身份，并保证一条 invite 只填充一个 target。
3. 记录 `created_by_user_id`、expiry 与 role intent。
4. accept 时使用统一 target matcher 校验 signed-in user 拥有匹配的 verified email/mobile。
5. 确保 user 在 owning organization 下有 active `organization_members` 行；只有 invitation policy 明确允许 workspace invite 创建 org membership 时才可创建。
6. 只把 user 加入被邀请的 workspace。
7. 物化 workspace member 与 `#all` scope projection。

如果 user 已有 inactive 或 suspended organization membership，invite 必须失败，不能静默重新激活。

v1 的克制路径是：

- existing organization member：直接走 direct member grant；
- external user：先通过现有 organization invite、directory sync、SSO/auto-join 或 workspace-local invite 成为 owning organization 下的 active member，再走 direct member grant；
- auth-side registration invitation 只解决登录/注册准入，不等价于 workspace access。

### 6.4 Revocation

撤销 direct 或 team-derived access 时必须：

1. 将 grant 标记为 inactive 或 revoked。
2. 基于剩余 direct/team grants 重新计算用户的 effective access。
3. 如果仍有 effective access，物化最高 effective role。
4. 如果不再有 effective access，且 workspace member 是 org projection 创建的，则 deactivate 该 workspace member，并把 message-space projections 标记为 removed。
5. 如果不再有 effective access，但用户已有 pre-existing baseline workspace role，则恢复 baseline role。
6. 保留历史 grant、projection、member 与 scope rows。

撤销 workspace access 不等于把用户移出 organization。

---

## 7. Scope Membership Invariant

`scope_memberships` 是 message-space projection。它表达某个 workspace member 可以看到或参与哪个 scope，例如 channel、thread、DM 或 task discussion。

workspace access materialization 必须保证：

- 每个 active human workspace member 都有 `#all` channel scope membership；
- 普通 private channel 不会因 organization access 自动加入；
- organization admin 不会被写入 `scope_memberships`，除非他自己获得 workspace access；
- revocation 通过 workspace member deactivation 使用的同一类 projection helper，将受影响 scope memberships 标记为 removed/ended。

这个约束保证 governance access 不会变成 message access。

---

## 8. UI 契约

### 8.1 Organization Workspaces

Organization Workspaces surface 应允许 `org_owner` 或 `org_admin`：

- 列出 organization-owned workspaces；
- 查看 access grants 与 team grants；
- 为 active organization members grant/revoke direct access；
- grant/revoke team access；
- 看到自己是否可以打开该 workspace。

如果 actor 缺少 `workspace.access`，UI 仍可展示治理控件，但内容入口应禁用，或替换为 access-management view。

v1 不在该 surface 内新增 external email/mobile invite；external user 应先通过已有 organization invite、directory sync、SSO/auto-join 或 workspace-local invite 进入 owning organization，再由 direct grant 获得 workspace access。任何新增 external invite UI 都必须使用 identity 领域统一的 invite identifier 解析和 mismatch 展示规则。

### 8.2 Workspace Member Management

Workspace member-management surface 应允许 workspace owners/admins：

- 邀请 external user 进入当前 workspace；
- 在 directory visibility 允许时，把 existing organization member 直接加入当前 workspace；
- 仅在 workspace role-management policy 允许时管理角色。

该 surface 不应暴露 organization-wide team grant 管理，除非 actor 同时拥有 organization admin rights。

---

## 9. Implementation Alignment

organization provider 应暴露一个统一 access-management service boundary，供两类 API family 调用。

推荐 service-level shape：

```text
ManageOrganizationWorkspaceAccess(actor_user_id, organization_id, workspace_id, operation)
```

授权 guard 应支持两种 mode：

- organization mode：actor 是 owning organization 下 active `org_owner` 或 `org_admin`；
- workspace mode：actor 是 active workspace member，并拥有所需 workspace management action。

direct member grant service method 不应同时要求两种 mode。目标关系是带 operation-specific constraints 的并集：

```text
allowed = organization_mode OR workspace_mode
```

team grants 仅允许 organization mode，除非未来规范明确把 team grant authority 委托给 workspace role。

---

## 10. Required Tests

该契约需要覆盖以下回归：

- 不属于 target workspace 的 `org_admin` 可以给 active organization member 授予 organization-owned workspace access；
- 同一个 `org_admin` 在没有 workspace access 时，仍不能读取 workspace messages、channels、DMs、threads、tasks、machine/agent runtime state；
- 被授予 access 的用户获得 active human `members` 行和 `#all` `scope_memberships` projection；
- direct grant revocation 只停用 org-derived workspace access，并保留 organization membership；
- team grant creation 会 reconcile 当前 active team members，并覆盖未来 team membership 变化；
- workspace admins 可以使用 workspace-local add/invite flows，但不会获得 organization team-grant authority；
- owner role changes 保证至少保留一个 effective workspace owner；
- access-management 测试不依赖 hard delete membership 或 grant rows。
