test(isolation): 测试里对共享角色的无限定 GRANT/REVOKE 是第六种「共享可变状态」—— 需一道机械门
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
## 发现:数据库授权是共享可变状态,而且没有任何门挡着
由 #413 / PR #516 的实现者在定位一条真红时发现,coord-chat-e2e 复核,**coord-main 独立核实并扩展**。
### 现象
它用 **`REVOKE INSERT ON chat_messages FROM app_rw`** 注入写回失败。**那是数据库级、全局的。**
而 `apps/api/vitest.config.ts:53` 是 **`maxWorkers: 4`** + forks 池 ⇒ **四个测试文件并行跑在同一个 Postgres 上**。别的文件的 fixture 只要在那个窗口里插 `chat_messages` 就挂。
### 实测(同语句、同角色、同库,不是推理)
```
revoke 前:org-f109badge 插入 chat_messages → INSERT 0 1
revoke 中:同一条 → ERROR: permission denied for table chat_messages
```
这同时解释了此前 pre-push 那次红在 `chat-db.ts:111` 的 `addChatMessage` —— **正是 chat_messages 的 INSERT**。
### 五组控制变量(实现者做全了)
| 条件 | 结果 |
|---|---|
| 分支单跑 | 绿 19/19 |
| base `fc0e1270` 单跑 | 绿 |
| main `775e3757` 单跑 | 绿 |
| main `775e3757` **全量** | **绿 435/435** |
| PR 合并提交 **全量** | 第 1 次绿;**第 2 次复现出与 CI 完全相同的那条断言** |
⇒ **不是某个 PR、不是合并组合,是全量并行才出现。**
---
## 本仓「共享可变状态」清单:**从五条变六条**
| # | 载体 | 已有防护 |
|---|---|---|
| 1 | git 索引 | ADR-005(worktree 隔离) |
| 2 | 工作树 | ADR-005 |
| 3 | stash 栈 | ADR-005 |
| 4 | 开发库 | `test-isolation`(但见 #468 / #487,端口与拆库两处仍有洞) |
| 5 | scratchpad | 约定 |
| 6 | **数据库授权(GRANT / REVOKE)** | **无** ← 本 issue |
第 6 条的特别之处:**它不在文件系统里,也不在 git 里**,所以既有的隔离手段(worktree、独立库名)**一条都挡不住** —— `REVOKE` 作用在角色上,同一个库里所有并行 worker 共享。
---
## 范围
### A. 一道机械门
禁止测试代码对 `app_rw` / `PUBLIC` 之类**共享角色**做**无限定**的 `GRANT` / `REVOKE`。
⚠ **不是禁止一切 GRANT/REVOKE** —— 实测现存有正当用法:
- `tests/chat/message-write-roundtrip.test.ts:79-81` 对**本 fixture 自己的 schema** 授权(限定到 `chat_wave2_fixture`,不影响别人);
- `tests/capability/model/*.test.ts` 只是**读迁移文本断言 GRANT 语句**,不执行。
⇒ 门要区分「**作用于共享对象**」与「**作用于本用例自己的对象**」。判据建议:语句里的对象是否被本文件独占(schema 前缀 / 表名带 fixture 标记)。
### B. 提供替代手段并写进文档
#516 的修法就是范例:换成**双重限定触发器**(本文件 org + sentinel body 前缀),DDL 从每用例两次降到每文件两次。**要注入权限失败,就限定到本用例数据。**
### C. 反证(不可省)
写一个对 `app_rw` 无限定 `REVOKE` 的测试 → 门必须变红;换成限定版 → 变绿。
⚠ **红了也要问是不是因为对的原因红的**(红线 10):确认它红在「无限定」这个判据上,不是红在「出现了 REVOKE 这个词」上。
---
## 与 #74 的关系
`vitest.config.ts` 里记着的全量套件非确定性(#74,仍 OPEN)**解释不了这次** —— 未修改的 main 全量 **435/435 全绿**。两者是不同的东西,本条不要并进 #74。
**Owner**:coord-architecture(test-isolation 属 harness)| **优先级**:P1
**关联**:#468(端口碰撞)、#487(拆库掐连接)—— 三条都是 `test-isolation` 那一层的洞,但载体各不相同。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with apps/api/vitest.config.ts:53 and the existing test-isolation setup, then inspect tests/chat/message-write-roundtrip.test.ts:79-81 and tests/capability/model/*.test.ts to distinguish executed permissions from migration-text assertions. Add the mechanical check and documentation so unqualified GRANT/REVOKE on shared roles fails while fixture-scoped statements pass. Add the required red/green counterexample and run the full parallel suite.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, typescript
- Domain
- databases, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100