@kodelyth/googlechat 2026.5.42 → 2026.6.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.
Files changed (61) hide show
  1. package/klaw.plugin.json +967 -2
  2. package/package.json +16 -4
  3. package/api.ts +0 -3
  4. package/channel-config-api.ts +0 -1
  5. package/channel-plugin-api.ts +0 -1
  6. package/config-api.ts +0 -2
  7. package/contract-api.ts +0 -5
  8. package/doctor-contract-api.ts +0 -1
  9. package/index.ts +0 -20
  10. package/runtime-api.ts +0 -55
  11. package/secret-contract-api.ts +0 -5
  12. package/setup-entry.ts +0 -13
  13. package/setup-plugin-api.ts +0 -3
  14. package/src/accounts.ts +0 -181
  15. package/src/actions.test.ts +0 -289
  16. package/src/actions.ts +0 -227
  17. package/src/api.ts +0 -316
  18. package/src/approval-auth.test.ts +0 -24
  19. package/src/approval-auth.ts +0 -32
  20. package/src/auth.ts +0 -218
  21. package/src/channel-config.test.ts +0 -39
  22. package/src/channel.adapters.ts +0 -340
  23. package/src/channel.deps.runtime.ts +0 -29
  24. package/src/channel.runtime.ts +0 -17
  25. package/src/channel.setup.ts +0 -98
  26. package/src/channel.test.ts +0 -784
  27. package/src/channel.ts +0 -277
  28. package/src/config-schema.test.ts +0 -31
  29. package/src/config-schema.ts +0 -3
  30. package/src/doctor-contract.test.ts +0 -75
  31. package/src/doctor-contract.ts +0 -182
  32. package/src/doctor.ts +0 -57
  33. package/src/gateway.ts +0 -63
  34. package/src/google-auth.runtime.test.ts +0 -543
  35. package/src/google-auth.runtime.ts +0 -568
  36. package/src/group-policy.ts +0 -17
  37. package/src/monitor-access.test.ts +0 -491
  38. package/src/monitor-access.ts +0 -465
  39. package/src/monitor-durable.test.ts +0 -39
  40. package/src/monitor-durable.ts +0 -23
  41. package/src/monitor-reply-delivery.ts +0 -156
  42. package/src/monitor-routing.ts +0 -65
  43. package/src/monitor-types.ts +0 -33
  44. package/src/monitor-webhook.test.ts +0 -587
  45. package/src/monitor-webhook.ts +0 -303
  46. package/src/monitor.reply-delivery.test.ts +0 -144
  47. package/src/monitor.test.ts +0 -159
  48. package/src/monitor.ts +0 -527
  49. package/src/monitor.webhook-routing.test.ts +0 -257
  50. package/src/runtime.ts +0 -9
  51. package/src/secret-contract.test.ts +0 -60
  52. package/src/secret-contract.ts +0 -161
  53. package/src/setup-core.ts +0 -40
  54. package/src/setup-surface.ts +0 -243
  55. package/src/setup.test.ts +0 -619
  56. package/src/targets.test.ts +0 -453
  57. package/src/targets.ts +0 -66
  58. package/src/types.config.ts +0 -3
  59. package/src/types.ts +0 -73
  60. package/test-api.ts +0 -2
  61. package/tsconfig.json +0 -16
@@ -1,491 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
2
-
3
- const createChannelPairingController = vi.hoisted(() => vi.fn());
4
- const isDangerousNameMatchingEnabled = vi.hoisted(() => vi.fn());
5
- const resolveAllowlistProviderRuntimeGroupPolicy = vi.hoisted(() => vi.fn());
6
- const resolveDefaultGroupPolicy = vi.hoisted(() => vi.fn());
7
- const warnMissingProviderGroupPolicyFallbackOnce = vi.hoisted(() => vi.fn());
8
- const sendGoogleChatMessage = vi.hoisted(() => vi.fn());
9
-
10
- vi.mock("../runtime-api.js", () => ({
11
- GROUP_POLICY_BLOCKED_LABEL: { space: "space" },
12
- createChannelPairingController,
13
- isDangerousNameMatchingEnabled,
14
- resolveAllowlistProviderRuntimeGroupPolicy,
15
- resolveDefaultGroupPolicy,
16
- warnMissingProviderGroupPolicyFallbackOnce,
17
- }));
18
-
19
- vi.mock("./api.js", () => ({
20
- sendGoogleChatMessage,
21
- }));
22
-
23
- function createCore() {
24
- return {
25
- channel: {
26
- commands: {
27
- shouldComputeCommandAuthorized: vi.fn(() => false),
28
- resolveCommandAuthorizedFromAuthorizers: vi.fn(() => false),
29
- shouldHandleTextCommands: vi.fn(() => false),
30
- isControlCommandMessage: vi.fn(() => false),
31
- },
32
- text: {
33
- hasControlCommand: vi.fn(() => false),
34
- },
35
- },
36
- };
37
- }
38
-
39
- function primeCommonDefaults() {
40
- isDangerousNameMatchingEnabled.mockReturnValue(false);
41
- resolveDefaultGroupPolicy.mockReturnValue("allowlist");
42
- resolveAllowlistProviderRuntimeGroupPolicy.mockReturnValue({
43
- groupPolicy: "allowlist",
44
- providerMissingFallbackApplied: false,
45
- });
46
- warnMissingProviderGroupPolicyFallbackOnce.mockReturnValue(undefined);
47
- }
48
-
49
- const baseAccessConfig = {
50
- channels: { googlechat: {} },
51
- commands: { useAccessGroups: true },
52
- } as const;
53
-
54
- const defaultSender = {
55
- senderId: "users/alice",
56
- senderName: "Alice",
57
- senderEmail: "alice@example.com",
58
- } as const;
59
-
60
- let applyGoogleChatInboundAccessPolicy: typeof import("./monitor-access.js").applyGoogleChatInboundAccessPolicy;
61
-
62
- function allowInboundGroupTraffic() {
63
- createChannelPairingController.mockReturnValue({
64
- readAllowFromStore: vi.fn(async () => []),
65
- issueChallenge: vi.fn(),
66
- });
67
- }
68
-
69
- async function applyInboundAccessPolicy(
70
- overrides: Partial<Parameters<typeof applyGoogleChatInboundAccessPolicy>[0]>,
71
- ) {
72
- return applyGoogleChatInboundAccessPolicy({
73
- account: {
74
- accountId: "default",
75
- config: {},
76
- } as never,
77
- config: baseAccessConfig as never,
78
- core: createCore() as never,
79
- space: { name: "spaces/AAA", displayName: "Team Room" } as never,
80
- message: { annotations: [] } as never,
81
- isGroup: true,
82
- rawBody: "hello team",
83
- logVerbose: vi.fn(),
84
- ...defaultSender,
85
- ...overrides,
86
- } as never);
87
- }
88
-
89
- describe("googlechat inbound access policy", () => {
90
- beforeAll(async () => {
91
- ({ applyGoogleChatInboundAccessPolicy } = await import("./monitor-access.js"));
92
- });
93
-
94
- afterAll(() => {
95
- vi.doUnmock("../runtime-api.js");
96
- vi.doUnmock("./api.js");
97
- vi.resetModules();
98
- });
99
-
100
- it.each([
101
- {
102
- name: "blocks raw email entries when dangerous name matching is disabled",
103
- allowNameMatching: false,
104
- allowFrom: ["jane@example.com"],
105
- senderId: "users/123",
106
- ok: false,
107
- },
108
- {
109
- name: "matches raw email entries when dangerous name matching is enabled",
110
- allowNameMatching: true,
111
- allowFrom: ["jane@example.com"],
112
- senderId: "users/123",
113
- ok: true,
114
- },
115
- {
116
- name: "does not treat users/<email> entries as email allowlist entries",
117
- allowNameMatching: true,
118
- allowFrom: ["users/jane@example.com"],
119
- senderId: "users/123",
120
- ok: false,
121
- },
122
- {
123
- name: "matches user id entries",
124
- allowNameMatching: false,
125
- allowFrom: ["users/abc"],
126
- senderId: "users/abc",
127
- ok: true,
128
- },
129
- ])("$name", async ({ allowNameMatching, allowFrom, senderId, ok }) => {
130
- primeCommonDefaults();
131
- isDangerousNameMatchingEnabled.mockReturnValue(allowNameMatching);
132
- createChannelPairingController.mockReturnValue({
133
- readAllowFromStore: vi.fn(async () => []),
134
- issueChallenge: vi.fn(),
135
- });
136
-
137
- const result = await applyInboundAccessPolicy({
138
- isGroup: false,
139
- account: {
140
- accountId: "default",
141
- config: {
142
- dm: {
143
- policy: "allowlist",
144
- allowFrom,
145
- },
146
- },
147
- } as never,
148
- senderId,
149
- senderEmail: "Jane@Example.com",
150
- });
151
- expect(result.ok).toBe(ok);
152
- });
153
-
154
- it("issues a pairing challenge for unauthorized DMs in pairing mode", async () => {
155
- primeCommonDefaults();
156
- const now = new Date("2026-05-09T06:35:00.000Z").getTime();
157
- const issueChallenge = vi.fn(async ({ onCreated, sendPairingReply }) => {
158
- onCreated?.();
159
- await sendPairingReply("pairing text");
160
- });
161
- createChannelPairingController.mockReturnValue({
162
- readAllowFromStore: vi.fn(async () => []),
163
- issueChallenge,
164
- });
165
- sendGoogleChatMessage.mockResolvedValue({ ok: true });
166
-
167
- const statusSink = vi.fn();
168
- const logVerbose = vi.fn();
169
- const account = {
170
- accountId: "default",
171
- config: {
172
- dm: { policy: "pairing" },
173
- },
174
- };
175
-
176
- vi.useFakeTimers();
177
- vi.setSystemTime(now);
178
- try {
179
- await expect(
180
- applyGoogleChatInboundAccessPolicy({
181
- account: account as never,
182
- config: {
183
- channels: { googlechat: {} },
184
- } as never,
185
- core: createCore() as never,
186
- space: { name: "spaces/AAA", displayName: "DM" } as never,
187
- message: { annotations: [] } as never,
188
- isGroup: false,
189
- senderId: "users/abc",
190
- senderName: "Alice",
191
- senderEmail: "alice@example.com",
192
- rawBody: "hello",
193
- statusSink,
194
- logVerbose,
195
- }),
196
- ).resolves.toEqual({ ok: false });
197
-
198
- expect(issueChallenge).toHaveBeenCalledTimes(1);
199
- expect(sendGoogleChatMessage).toHaveBeenCalledWith({
200
- account,
201
- space: "spaces/AAA",
202
- text: "pairing text",
203
- });
204
- expect(statusSink).toHaveBeenCalledWith({
205
- lastOutboundAt: now,
206
- });
207
- } finally {
208
- vi.useRealTimers();
209
- }
210
- });
211
-
212
- it("allows group traffic when sender and mention gates pass", async () => {
213
- primeCommonDefaults();
214
- allowInboundGroupTraffic();
215
- const core = createCore();
216
- core.channel.commands.shouldComputeCommandAuthorized.mockReturnValue(true);
217
- core.channel.commands.resolveCommandAuthorizedFromAuthorizers.mockReturnValue(true);
218
-
219
- await expect(
220
- applyInboundAccessPolicy({
221
- account: {
222
- accountId: "default",
223
- config: {
224
- botUser: "users/app-bot",
225
- groups: {
226
- "spaces/AAA": {
227
- users: ["users/alice"],
228
- requireMention: true,
229
- systemPrompt: " group prompt ",
230
- },
231
- },
232
- },
233
- } as never,
234
- core: core as never,
235
- message: {
236
- annotations: [
237
- {
238
- type: "USER_MENTION",
239
- userMention: { user: { name: "users/app-bot" } },
240
- },
241
- ],
242
- } as never,
243
- }),
244
- ).resolves.toEqual({
245
- ok: true,
246
- commandAuthorized: true,
247
- effectiveWasMentioned: true,
248
- groupSystemPrompt: "group prompt",
249
- });
250
- });
251
-
252
- it("allows group traffic from generic message sender access groups", async () => {
253
- primeCommonDefaults();
254
- allowInboundGroupTraffic();
255
-
256
- const result = await applyInboundAccessPolicy({
257
- config: {
258
- ...baseAccessConfig,
259
- accessGroups: {
260
- operators: {
261
- type: "message.senders",
262
- members: {
263
- googlechat: ["users/alice"],
264
- },
265
- },
266
- },
267
- } as never,
268
- account: {
269
- accountId: "default",
270
- config: {
271
- groups: {
272
- "spaces/AAA": {
273
- users: ["accessGroup:operators"],
274
- requireMention: false,
275
- },
276
- },
277
- },
278
- } as never,
279
- });
280
- expect(result.ok).toBe(true);
281
- });
282
-
283
- it("expands generic message sender access groups before DM access checks", async () => {
284
- primeCommonDefaults();
285
- const readAllowFromStore = vi.fn(async () => []);
286
- createChannelPairingController.mockReturnValue({
287
- readAllowFromStore,
288
- issueChallenge: vi.fn(),
289
- });
290
-
291
- const result = await applyInboundAccessPolicy({
292
- isGroup: false,
293
- config: {
294
- ...baseAccessConfig,
295
- accessGroups: {
296
- operators: {
297
- type: "message.senders",
298
- members: {
299
- googlechat: ["users/alice"],
300
- },
301
- },
302
- },
303
- } as never,
304
- account: {
305
- accountId: "default",
306
- config: {
307
- dm: {
308
- policy: "allowlist",
309
- allowFrom: ["accessGroup:operators"],
310
- },
311
- },
312
- } as never,
313
- });
314
- expect(result.ok).toBe(true);
315
-
316
- expect(readAllowFromStore).not.toHaveBeenCalled();
317
- });
318
-
319
- it("preserves allowlist group policy when a routed space has no sender allowlist", async () => {
320
- primeCommonDefaults();
321
- allowInboundGroupTraffic();
322
- const logVerbose = vi.fn();
323
-
324
- await expect(
325
- applyInboundAccessPolicy({
326
- account: {
327
- accountId: "default",
328
- config: {
329
- dm: {
330
- policy: "allowlist",
331
- allowFrom: ["users/alice"],
332
- },
333
- groups: {
334
- "spaces/AAA": {
335
- enabled: true,
336
- },
337
- },
338
- },
339
- } as never,
340
- logVerbose,
341
- }),
342
- ).resolves.toEqual({ ok: false });
343
-
344
- expect(logVerbose).toHaveBeenCalledWith(
345
- "drop group message (sender policy blocked, reason=groupPolicy=allowlist (empty allowlist), space=spaces/AAA)",
346
- );
347
- });
348
-
349
- it("keeps configured space users sender-scoped when group policy is open", async () => {
350
- primeCommonDefaults();
351
- resolveAllowlistProviderRuntimeGroupPolicy.mockReturnValue({
352
- groupPolicy: "open",
353
- providerMissingFallbackApplied: false,
354
- });
355
- allowInboundGroupTraffic();
356
- const logVerbose = vi.fn();
357
-
358
- await expect(
359
- applyInboundAccessPolicy({
360
- account: {
361
- accountId: "default",
362
- config: {
363
- groupPolicy: "open",
364
- groups: {
365
- "spaces/AAA": {
366
- users: ["users/bob"],
367
- requireMention: false,
368
- },
369
- },
370
- },
371
- } as never,
372
- logVerbose,
373
- }),
374
- ).resolves.toEqual({ ok: false });
375
-
376
- expect(logVerbose).toHaveBeenCalledWith("drop group message (sender not allowed, users/alice)");
377
- });
378
-
379
- it("drops unauthorized group control commands", async () => {
380
- primeCommonDefaults();
381
- allowInboundGroupTraffic();
382
- resolveAllowlistProviderRuntimeGroupPolicy.mockReturnValue({
383
- groupPolicy: "open",
384
- providerMissingFallbackApplied: false,
385
- });
386
- const core = createCore();
387
- core.channel.commands.shouldComputeCommandAuthorized.mockReturnValue(true);
388
- core.channel.commands.isControlCommandMessage.mockReturnValue(true);
389
- const logVerbose = vi.fn();
390
-
391
- await expect(
392
- applyInboundAccessPolicy({
393
- core: core as never,
394
- account: {
395
- accountId: "default",
396
- config: {
397
- groups: {
398
- "spaces/AAA": {
399
- requireMention: false,
400
- },
401
- },
402
- },
403
- } as never,
404
- rawBody: "/admin",
405
- logVerbose,
406
- }),
407
- ).resolves.toEqual({ ok: false });
408
-
409
- expect(logVerbose).toHaveBeenCalledWith("googlechat: drop control command from users/alice");
410
- });
411
-
412
- it("does not match group policy by mutable space displayName when the stable id differs", async () => {
413
- primeCommonDefaults();
414
- allowInboundGroupTraffic();
415
- const logVerbose = vi.fn();
416
-
417
- await expect(
418
- applyInboundAccessPolicy({
419
- account: {
420
- accountId: "default",
421
- config: {
422
- groups: {
423
- "Finance Ops": {
424
- users: ["users/alice"],
425
- requireMention: true,
426
- systemPrompt: "finance-only prompt",
427
- },
428
- },
429
- },
430
- } as never,
431
- core: createCore() as never,
432
- space: { name: "spaces/BBB", displayName: "Finance Ops" } as never,
433
- message: {
434
- annotations: [
435
- {
436
- type: "USER_MENTION",
437
- userMention: { user: { name: "users/app" } },
438
- },
439
- ],
440
- } as never,
441
- rawBody: "show quarter close status",
442
- logVerbose,
443
- }),
444
- ).resolves.toEqual({ ok: false });
445
-
446
- expect(logVerbose).toHaveBeenCalledWith(
447
- "Deprecated Google Chat group key detected: group routing now requires stable space ids (spaces/<spaceId>). Update channels.googlechat.groups keys: Finance Ops",
448
- );
449
- expect(logVerbose).toHaveBeenCalledWith(
450
- "drop group message (deprecated mutable group key matched, space=spaces/BBB)",
451
- );
452
- });
453
-
454
- it("fails closed instead of falling back to wildcard when a deprecated room key matches", async () => {
455
- primeCommonDefaults();
456
- resolveAllowlistProviderRuntimeGroupPolicy.mockReturnValue({
457
- groupPolicy: "open",
458
- providerMissingFallbackApplied: false,
459
- });
460
- allowInboundGroupTraffic();
461
- const logVerbose = vi.fn();
462
-
463
- await expect(
464
- applyInboundAccessPolicy({
465
- account: {
466
- accountId: "default",
467
- config: {
468
- groupPolicy: "open",
469
- groups: {
470
- "*": {
471
- users: ["users/alice"],
472
- },
473
- "Finance Ops": {
474
- enabled: false,
475
- users: ["users/bob"],
476
- },
477
- },
478
- },
479
- } as never,
480
- core: createCore() as never,
481
- space: { name: "spaces/BBB", displayName: "Finance Ops" } as never,
482
- rawBody: "show quarter close status",
483
- logVerbose,
484
- }),
485
- ).resolves.toEqual({ ok: false });
486
-
487
- expect(logVerbose).toHaveBeenCalledWith(
488
- "drop group message (deprecated mutable group key matched, space=spaces/BBB)",
489
- );
490
- });
491
- });