tmux-team 2.2.0 → 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -191
- package/package.json +1 -1
- package/src/cli.ts +0 -5
- package/src/commands/config.ts +2 -44
- package/src/commands/help.ts +0 -2
- package/src/commands/talk.test.ts +296 -46
- package/src/commands/talk.ts +69 -63
- package/src/config.test.ts +0 -1
- package/src/config.ts +0 -1
- package/src/identity.ts +89 -0
- package/src/types.ts +2 -2
- package/src/version.ts +1 -1
- package/src/pm/commands.test.ts +0 -1462
- package/src/pm/commands.ts +0 -1011
- package/src/pm/manager.test.ts +0 -377
- package/src/pm/manager.ts +0 -146
- package/src/pm/permissions.test.ts +0 -444
- package/src/pm/permissions.ts +0 -293
- package/src/pm/storage/adapter.ts +0 -57
- package/src/pm/storage/fs.test.ts +0 -512
- package/src/pm/storage/fs.ts +0 -290
- package/src/pm/storage/github.ts +0 -842
- package/src/pm/types.ts +0 -91
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// Permission System Tests
|
|
3
|
-
// ─────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
buildPermissionPath,
|
|
8
|
-
checkPermission,
|
|
9
|
-
getCurrentActor,
|
|
10
|
-
resolveActor,
|
|
11
|
-
PermissionChecks,
|
|
12
|
-
} from './permissions.js';
|
|
13
|
-
import type { ResolvedConfig } from '../types.js';
|
|
14
|
-
|
|
15
|
-
function createMockConfig(agents: Record<string, { deny?: string[] }>): ResolvedConfig {
|
|
16
|
-
return {
|
|
17
|
-
mode: 'polling',
|
|
18
|
-
preambleMode: 'always',
|
|
19
|
-
defaults: {
|
|
20
|
-
timeout: 60,
|
|
21
|
-
pollInterval: 1,
|
|
22
|
-
captureLines: 100,
|
|
23
|
-
preambleEvery: 3,
|
|
24
|
-
hideOrphanTasks: false,
|
|
25
|
-
},
|
|
26
|
-
agents,
|
|
27
|
-
paneRegistry: {},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('buildPermissionPath', () => {
|
|
32
|
-
it('builds path without fields', () => {
|
|
33
|
-
expect(buildPermissionPath({ resource: 'task', action: 'list', fields: [] })).toBe(
|
|
34
|
-
'pm:task:list'
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('builds path with single field', () => {
|
|
39
|
-
expect(buildPermissionPath({ resource: 'task', action: 'update', fields: ['status'] })).toBe(
|
|
40
|
-
'pm:task:update(status)'
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('builds path with multiple fields sorted alphabetically', () => {
|
|
45
|
-
expect(
|
|
46
|
-
buildPermissionPath({ resource: 'task', action: 'update', fields: ['status', 'assignee'] })
|
|
47
|
-
).toBe('pm:task:update(assignee,status)');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('builds path with fields already sorted', () => {
|
|
51
|
-
expect(
|
|
52
|
-
buildPermissionPath({ resource: 'task', action: 'update', fields: ['assignee', 'status'] })
|
|
53
|
-
).toBe('pm:task:update(assignee,status)');
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('getCurrentActor', () => {
|
|
58
|
-
const originalEnv = { ...process.env };
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
process.env = { ...originalEnv };
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('returns TMT_AGENT_NAME if set', () => {
|
|
65
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
66
|
-
delete process.env.TMUX_TEAM_ACTOR;
|
|
67
|
-
expect(getCurrentActor()).toBe('codex');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('returns TMUX_TEAM_ACTOR if TMT_AGENT_NAME not set', () => {
|
|
71
|
-
delete process.env.TMT_AGENT_NAME;
|
|
72
|
-
process.env.TMUX_TEAM_ACTOR = 'gemini';
|
|
73
|
-
expect(getCurrentActor()).toBe('gemini');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('returns human if no env vars set', () => {
|
|
77
|
-
delete process.env.TMT_AGENT_NAME;
|
|
78
|
-
delete process.env.TMUX_TEAM_ACTOR;
|
|
79
|
-
expect(getCurrentActor()).toBe('human');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('prefers TMT_AGENT_NAME over TMUX_TEAM_ACTOR', () => {
|
|
83
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
84
|
-
process.env.TMUX_TEAM_ACTOR = 'gemini';
|
|
85
|
-
expect(getCurrentActor()).toBe('codex');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('checkPermission', () => {
|
|
90
|
-
const originalEnv = { ...process.env };
|
|
91
|
-
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
process.env = { ...originalEnv };
|
|
94
|
-
// Disable pane detection in tests by unsetting TMUX
|
|
95
|
-
delete process.env.TMUX;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
afterEach(() => {
|
|
99
|
-
process.env = { ...originalEnv };
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('allows everything for human actor', () => {
|
|
103
|
-
delete process.env.TMT_AGENT_NAME;
|
|
104
|
-
delete process.env.TMUX_TEAM_ACTOR;
|
|
105
|
-
|
|
106
|
-
const config = createMockConfig({
|
|
107
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('allows when no deny patterns for agent', () => {
|
|
114
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
115
|
-
|
|
116
|
-
const config = createMockConfig({
|
|
117
|
-
codex: {}, // No deny patterns
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('allows when agent not in config', () => {
|
|
124
|
-
process.env.TMT_AGENT_NAME = 'unknown-agent';
|
|
125
|
-
|
|
126
|
-
const config = createMockConfig({
|
|
127
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('denies when pattern matches exactly', () => {
|
|
134
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
135
|
-
|
|
136
|
-
const config = createMockConfig({
|
|
137
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('denies when pattern matches any field', () => {
|
|
144
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
145
|
-
|
|
146
|
-
const config = createMockConfig({
|
|
147
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Using both status and assignee, should still be denied because status is in deny list
|
|
151
|
-
expect(
|
|
152
|
-
checkPermission(config, PermissionChecks.taskUpdate(['status', 'assignee'])).allowed
|
|
153
|
-
).toBe(false);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('allows when fields do not match', () => {
|
|
157
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
158
|
-
|
|
159
|
-
const config = createMockConfig({
|
|
160
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// Only updating assignee, not status
|
|
164
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['assignee'])).allowed).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('denies entire action when pattern has no fields', () => {
|
|
168
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
169
|
-
|
|
170
|
-
const config = createMockConfig({
|
|
171
|
-
codex: { deny: ['pm:task:update'] },
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Any update should be denied
|
|
175
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
176
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['assignee'])).allowed).toBe(false);
|
|
177
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate([])).allowed).toBe(false);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('denies when wildcard pattern matches any field', () => {
|
|
181
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
182
|
-
|
|
183
|
-
const config = createMockConfig({
|
|
184
|
-
codex: { deny: ['pm:task:update(*)'] },
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
188
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['assignee'])).allowed).toBe(false);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('allows no-field action when wildcard is used', () => {
|
|
192
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
193
|
-
|
|
194
|
-
const config = createMockConfig({
|
|
195
|
-
codex: { deny: ['pm:task:update(*)'] },
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Wildcard only matches when fields are present
|
|
199
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate([])).allowed).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('allows different resource', () => {
|
|
203
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
204
|
-
|
|
205
|
-
const config = createMockConfig({
|
|
206
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
expect(checkPermission(config, PermissionChecks.milestoneUpdate(['status'])).allowed).toBe(
|
|
210
|
-
true
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('allows different action', () => {
|
|
215
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
216
|
-
|
|
217
|
-
const config = createMockConfig({
|
|
218
|
-
codex: { deny: ['pm:task:update(status)'] },
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
expect(checkPermission(config, PermissionChecks.taskCreate()).allowed).toBe(true);
|
|
222
|
-
expect(checkPermission(config, PermissionChecks.taskList()).allowed).toBe(true);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('handles multiple deny patterns', () => {
|
|
226
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
227
|
-
|
|
228
|
-
const config = createMockConfig({
|
|
229
|
-
codex: {
|
|
230
|
-
deny: ['pm:task:update(status)', 'pm:milestone:update(status)', 'pm:task:delete'],
|
|
231
|
-
},
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
235
|
-
expect(checkPermission(config, PermissionChecks.milestoneUpdate(['status'])).allowed).toBe(
|
|
236
|
-
false
|
|
237
|
-
);
|
|
238
|
-
expect(checkPermission(config, PermissionChecks.taskDelete()).allowed).toBe(false);
|
|
239
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['assignee'])).allowed).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('PermissionChecks helpers', () => {
|
|
244
|
-
it('creates correct task checks', () => {
|
|
245
|
-
expect(PermissionChecks.taskList()).toEqual({ resource: 'task', action: 'list', fields: [] });
|
|
246
|
-
expect(PermissionChecks.taskShow()).toEqual({ resource: 'task', action: 'show', fields: [] });
|
|
247
|
-
expect(PermissionChecks.taskCreate()).toEqual({
|
|
248
|
-
resource: 'task',
|
|
249
|
-
action: 'create',
|
|
250
|
-
fields: [],
|
|
251
|
-
});
|
|
252
|
-
expect(PermissionChecks.taskUpdate(['status'])).toEqual({
|
|
253
|
-
resource: 'task',
|
|
254
|
-
action: 'update',
|
|
255
|
-
fields: ['status'],
|
|
256
|
-
});
|
|
257
|
-
expect(PermissionChecks.taskDelete()).toEqual({
|
|
258
|
-
resource: 'task',
|
|
259
|
-
action: 'delete',
|
|
260
|
-
fields: [],
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('creates correct milestone checks', () => {
|
|
265
|
-
expect(PermissionChecks.milestoneList()).toEqual({
|
|
266
|
-
resource: 'milestone',
|
|
267
|
-
action: 'list',
|
|
268
|
-
fields: [],
|
|
269
|
-
});
|
|
270
|
-
expect(PermissionChecks.milestoneCreate()).toEqual({
|
|
271
|
-
resource: 'milestone',
|
|
272
|
-
action: 'create',
|
|
273
|
-
fields: [],
|
|
274
|
-
});
|
|
275
|
-
expect(PermissionChecks.milestoneUpdate(['status'])).toEqual({
|
|
276
|
-
resource: 'milestone',
|
|
277
|
-
action: 'update',
|
|
278
|
-
fields: ['status'],
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('creates correct doc checks', () => {
|
|
283
|
-
expect(PermissionChecks.docRead()).toEqual({ resource: 'doc', action: 'read', fields: [] });
|
|
284
|
-
expect(PermissionChecks.docUpdate()).toEqual({ resource: 'doc', action: 'update', fields: [] });
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it('creates correct team checks', () => {
|
|
288
|
-
expect(PermissionChecks.teamCreate()).toEqual({
|
|
289
|
-
resource: 'team',
|
|
290
|
-
action: 'create',
|
|
291
|
-
fields: [],
|
|
292
|
-
});
|
|
293
|
-
expect(PermissionChecks.teamList()).toEqual({ resource: 'team', action: 'list', fields: [] });
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('creates correct log checks', () => {
|
|
297
|
-
expect(PermissionChecks.logRead()).toEqual({ resource: 'log', action: 'read', fields: [] });
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
describe('resolveActor', () => {
|
|
302
|
-
const originalEnv = { ...process.env };
|
|
303
|
-
|
|
304
|
-
afterEach(() => {
|
|
305
|
-
process.env = { ...originalEnv };
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('returns human when not in tmux and no env var', () => {
|
|
309
|
-
delete process.env.TMUX;
|
|
310
|
-
delete process.env.TMT_AGENT_NAME;
|
|
311
|
-
delete process.env.TMUX_TEAM_ACTOR;
|
|
312
|
-
|
|
313
|
-
const result = resolveActor({});
|
|
314
|
-
expect(result.actor).toBe('human');
|
|
315
|
-
expect(result.source).toBe('default');
|
|
316
|
-
expect(result.warning).toBeUndefined();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('uses env var when not in tmux', () => {
|
|
320
|
-
delete process.env.TMUX;
|
|
321
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
322
|
-
|
|
323
|
-
const result = resolveActor({});
|
|
324
|
-
expect(result.actor).toBe('codex');
|
|
325
|
-
expect(result.source).toBe('env');
|
|
326
|
-
expect(result.warning).toBeUndefined();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('prefers TMT_AGENT_NAME over TMUX_TEAM_ACTOR', () => {
|
|
330
|
-
delete process.env.TMUX;
|
|
331
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
332
|
-
process.env.TMUX_TEAM_ACTOR = 'gemini';
|
|
333
|
-
|
|
334
|
-
const result = resolveActor({});
|
|
335
|
-
expect(result.actor).toBe('codex');
|
|
336
|
-
expect(result.source).toBe('env');
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe('checkPermission with local config (integration)', () => {
|
|
341
|
-
const originalEnv = { ...process.env };
|
|
342
|
-
|
|
343
|
-
beforeEach(() => {
|
|
344
|
-
process.env = { ...originalEnv };
|
|
345
|
-
delete process.env.TMUX;
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
afterEach(() => {
|
|
349
|
-
process.env = { ...originalEnv };
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// Helper to create config as if loaded from local tmux-team.json
|
|
353
|
-
function createConfigWithLocalPermissions(
|
|
354
|
-
localAgents: Record<string, { preamble?: string; deny?: string[] }>
|
|
355
|
-
): ResolvedConfig {
|
|
356
|
-
return {
|
|
357
|
-
mode: 'polling',
|
|
358
|
-
preambleMode: 'always',
|
|
359
|
-
defaults: {
|
|
360
|
-
timeout: 60,
|
|
361
|
-
pollInterval: 1,
|
|
362
|
-
captureLines: 100,
|
|
363
|
-
preambleEvery: 3,
|
|
364
|
-
hideOrphanTasks: false,
|
|
365
|
-
},
|
|
366
|
-
agents: localAgents, // This simulates merged local config
|
|
367
|
-
paneRegistry: {},
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
it('enforces local deny rules for specific agent', () => {
|
|
372
|
-
process.env.TMT_AGENT_NAME = 'claude';
|
|
373
|
-
|
|
374
|
-
// Simulates local config with: claude has deny rules, codex does not
|
|
375
|
-
const config = createConfigWithLocalPermissions({
|
|
376
|
-
claude: { deny: ['pm:task:update(status)', 'pm:milestone:update(status)'] },
|
|
377
|
-
codex: { preamble: 'Code quality guard' }, // No deny
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Claude is blocked from status updates
|
|
381
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
382
|
-
expect(checkPermission(config, PermissionChecks.milestoneUpdate(['status'])).allowed).toBe(
|
|
383
|
-
false
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
// Claude can still do other things
|
|
387
|
-
expect(checkPermission(config, PermissionChecks.taskCreate()).allowed).toBe(true);
|
|
388
|
-
expect(checkPermission(config, PermissionChecks.taskList()).allowed).toBe(true);
|
|
389
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['assignee'])).allowed).toBe(true);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('allows agent without deny rules to do everything', () => {
|
|
393
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
394
|
-
|
|
395
|
-
const config = createConfigWithLocalPermissions({
|
|
396
|
-
claude: { deny: ['pm:task:update(status)'] },
|
|
397
|
-
codex: { preamble: 'Code quality guard' }, // No deny
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
// Codex can do everything including status updates
|
|
401
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(true);
|
|
402
|
-
expect(checkPermission(config, PermissionChecks.milestoneUpdate(['status'])).allowed).toBe(
|
|
403
|
-
true
|
|
404
|
-
);
|
|
405
|
-
expect(checkPermission(config, PermissionChecks.taskCreate()).allowed).toBe(true);
|
|
406
|
-
expect(checkPermission(config, PermissionChecks.taskDelete()).allowed).toBe(true);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it('project-specific permissions: implementer vs reviewer roles', () => {
|
|
410
|
-
// Real-world scenario: claude implements, codex reviews
|
|
411
|
-
const config = createConfigWithLocalPermissions({
|
|
412
|
-
claude: {
|
|
413
|
-
preamble: 'You implement features. Ask Codex for review before marking done.',
|
|
414
|
-
deny: ['pm:task:update(status)', 'pm:milestone:update(status)'],
|
|
415
|
-
},
|
|
416
|
-
codex: {
|
|
417
|
-
preamble: 'You are the code quality guard. Mark tasks done after reviewing.',
|
|
418
|
-
// No deny - codex can update status
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Claude cannot mark tasks done
|
|
423
|
-
process.env.TMT_AGENT_NAME = 'claude';
|
|
424
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(false);
|
|
425
|
-
|
|
426
|
-
// Codex can mark tasks done
|
|
427
|
-
process.env.TMT_AGENT_NAME = 'codex';
|
|
428
|
-
expect(checkPermission(config, PermissionChecks.taskUpdate(['status'])).allowed).toBe(true);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('returns correct result when permission denied', () => {
|
|
432
|
-
process.env.TMT_AGENT_NAME = 'claude';
|
|
433
|
-
|
|
434
|
-
const config = createConfigWithLocalPermissions({
|
|
435
|
-
claude: { deny: ['pm:task:update(status)'] },
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
const result = checkPermission(config, PermissionChecks.taskUpdate(['status']));
|
|
439
|
-
|
|
440
|
-
expect(result.allowed).toBe(false);
|
|
441
|
-
expect(result.actor).toBe('claude');
|
|
442
|
-
expect(result.source).toBe('env');
|
|
443
|
-
});
|
|
444
|
-
});
|