@vellumai/assistant 0.4.33 → 0.4.35
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/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -3
- package/src/__tests__/actor-token-service.test.ts +4 -11
- package/src/__tests__/approval-primitive.test.ts +0 -45
- package/src/__tests__/assistant-id-boundary-guard.test.ts +169 -0
- package/src/__tests__/callback-handoff-copy.test.ts +0 -1
- package/src/__tests__/channel-approval-routes.test.ts +5 -45
- package/src/__tests__/channel-guardian.test.ts +122 -345
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -3
- package/src/__tests__/contacts-tools.test.ts +4 -5
- package/src/__tests__/conversation-attention-store.test.ts +2 -65
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -2
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -7
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -74
- package/src/__tests__/guardian-action-late-reply.test.ts +1 -8
- package/src/__tests__/guardian-grant-minting.test.ts +0 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -3
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +0 -7
- package/src/__tests__/notification-broadcaster.test.ts +1 -2
- package/src/__tests__/notification-decision-fallback.test.ts +0 -2
- package/src/__tests__/notification-decision-strategy.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +11 -83
- package/src/__tests__/scoped-approval-grants.test.ts +9 -40
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -36
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/send-notification-tool.test.ts +0 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -4
- package/src/__tests__/thread-seed-composer.test.ts +0 -1
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -4
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -5
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -17
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -13
- package/src/__tests__/trusted-contact-verification.test.ts +3 -15
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -37
- package/src/approvals/approval-primitive.ts +0 -15
- package/src/approvals/guardian-decision-primitive.ts +0 -3
- package/src/approvals/guardian-request-resolvers.ts +0 -5
- package/src/calls/call-domain.ts +0 -3
- package/src/calls/call-store.ts +0 -3
- package/src/calls/guardian-action-sweep.ts +2 -1
- package/src/calls/guardian-dispatch.ts +1 -2
- package/src/calls/relay-access-wait.ts +0 -4
- package/src/calls/relay-server.ts +3 -11
- package/src/calls/relay-setup-router.ts +1 -2
- package/src/calls/relay-verification.ts +0 -1
- package/src/calls/twilio-routes.ts +0 -3
- package/src/calls/types.ts +0 -1
- package/src/calls/voice-session-bridge.ts +0 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +100 -171
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -1
- package/src/contacts/contact-store.ts +13 -88
- package/src/contacts/contacts-write.ts +3 -11
- package/src/contacts/types.ts +0 -1
- package/src/daemon/handlers/config-channels.ts +16 -42
- package/src/daemon/handlers/config-inbox.ts +6 -6
- package/src/daemon/handlers/contacts.ts +3 -11
- package/src/daemon/handlers/index.ts +0 -2
- package/src/daemon/session-process.ts +0 -4
- package/src/memory/conversation-attention-store.ts +4 -19
- package/src/memory/conversation-crud.ts +0 -2
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +0 -12
- package/src/memory/guardian-approvals.ts +35 -80
- package/src/memory/guardian-rate-limits.ts +1 -14
- package/src/memory/guardian-verification.ts +6 -34
- package/src/memory/invite-store.ts +5 -14
- package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
- package/src/memory/migrations/038-actor-token-records.ts +8 -1
- package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
- package/src/memory/migrations/110-channel-guardian.ts +27 -6
- package/src/memory/migrations/112-assistant-inbox.ts +39 -15
- package/src/memory/migrations/114-notifications.ts +37 -15
- package/src/memory/migrations/117-conversation-attention.ts +33 -9
- package/src/memory/migrations/134-contacts-notes-column.ts +64 -45
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +263 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +14 -1
- package/src/memory/migrations/schema-introspection.ts +18 -0
- package/src/memory/schema/calls.ts +0 -7
- package/src/memory/schema/contacts.ts +0 -8
- package/src/memory/schema/guardian.ts +0 -5
- package/src/memory/schema/infrastructure.ts +0 -2
- package/src/memory/schema/notifications.ts +3 -17
- package/src/memory/scoped-approval-grants.ts +2 -24
- package/src/notifications/adapters/sms.ts +2 -1
- package/src/notifications/broadcaster.ts +1 -6
- package/src/notifications/decision-engine.ts +3 -4
- package/src/notifications/deliveries-store.ts +0 -4
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/deterministic-checks.ts +1 -6
- package/src/notifications/emit-signal.ts +4 -11
- package/src/notifications/events-store.ts +7 -17
- package/src/notifications/preference-summary.ts +2 -2
- package/src/notifications/preferences-store.ts +2 -9
- package/src/notifications/signal.ts +0 -1
- package/src/notifications/thread-candidates.ts +1 -11
- package/src/notifications/types.ts +0 -3
- package/src/runtime/access-request-helper.ts +3 -10
- package/src/runtime/actor-refresh-token-store.ts +0 -6
- package/src/runtime/actor-token-store.ts +3 -16
- package/src/runtime/actor-trust-resolver.ts +1 -4
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -9
- package/src/runtime/auth/credential-service.ts +1 -15
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/channel-guardian-service.ts +15 -46
- package/src/runtime/channel-invite-transport.ts +8 -0
- package/src/runtime/channel-invite-transports/email.ts +4 -0
- package/src/runtime/channel-invite-transports/slack.ts +6 -0
- package/src/runtime/channel-invite-transports/sms.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +0 -1
- package/src/runtime/guardian-action-followup-executor.ts +3 -2
- package/src/runtime/guardian-action-grant-minter.ts +0 -1
- package/src/runtime/guardian-outbound-actions.ts +2 -12
- package/src/runtime/guardian-vellum-migration.ts +2 -3
- package/src/runtime/http-server.ts +3 -10
- package/src/runtime/http-types.ts +13 -1
- package/src/runtime/invite-redemption-service.ts +1 -14
- package/src/runtime/local-actor-identity.ts +2 -5
- package/src/runtime/routes/access-request-decision.ts +0 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -9
- package/src/runtime/routes/channel-readiness-routes.ts +29 -18
- package/src/runtime/routes/contact-routes.ts +15 -40
- package/src/runtime/routes/conversation-attention-routes.ts +0 -2
- package/src/runtime/routes/global-search-routes.ts +0 -2
- package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -7
- package/src/runtime/routes/guardian-expiry-sweep.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +0 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -43
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -4
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -1
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +0 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -7
- package/src/runtime/routes/pairing-routes.ts +4 -4
- package/src/runtime/routes/surface-content-routes.ts +104 -0
- package/src/runtime/tool-grant-request-helper.ts +0 -1
- package/src/tools/browser/browser-manager.ts +22 -21
- package/src/tools/browser/runtime-check.ts +111 -6
- package/src/tools/calls/call-start.ts +1 -3
- package/src/tools/followups/followup_create.ts +1 -2
- package/src/tools/tool-approval-handler.ts +0 -2
|
@@ -207,7 +207,6 @@ describe("verification challenge lifecycle", () => {
|
|
|
207
207
|
test("createChallenge creates a pending challenge", () => {
|
|
208
208
|
const challenge = createChallenge({
|
|
209
209
|
id: "chal-1",
|
|
210
|
-
assistantId: "asst-1",
|
|
211
210
|
channel: "telegram",
|
|
212
211
|
challengeHash: "abc123hash",
|
|
213
212
|
expiresAt: Date.now() + 600_000,
|
|
@@ -223,17 +222,12 @@ describe("verification challenge lifecycle", () => {
|
|
|
223
222
|
test("findPendingChallengeByHash finds a matching pending challenge", () => {
|
|
224
223
|
createChallenge({
|
|
225
224
|
id: "chal-1",
|
|
226
|
-
assistantId: "asst-1",
|
|
227
225
|
channel: "telegram",
|
|
228
226
|
challengeHash: "abc123hash",
|
|
229
227
|
expiresAt: Date.now() + 600_000,
|
|
230
228
|
});
|
|
231
229
|
|
|
232
|
-
const found = findPendingChallengeByHash(
|
|
233
|
-
"asst-1",
|
|
234
|
-
"telegram",
|
|
235
|
-
"abc123hash",
|
|
236
|
-
);
|
|
230
|
+
const found = findPendingChallengeByHash("telegram", "abc123hash");
|
|
237
231
|
expect(found).not.toBeNull();
|
|
238
232
|
expect(found!.id).toBe("chal-1");
|
|
239
233
|
});
|
|
@@ -241,41 +235,30 @@ describe("verification challenge lifecycle", () => {
|
|
|
241
235
|
test("findPendingChallengeByHash returns null for wrong hash", () => {
|
|
242
236
|
createChallenge({
|
|
243
237
|
id: "chal-1",
|
|
244
|
-
assistantId: "asst-1",
|
|
245
238
|
channel: "telegram",
|
|
246
239
|
challengeHash: "abc123hash",
|
|
247
240
|
expiresAt: Date.now() + 600_000,
|
|
248
241
|
});
|
|
249
242
|
|
|
250
|
-
const found = findPendingChallengeByHash(
|
|
251
|
-
"asst-1",
|
|
252
|
-
"telegram",
|
|
253
|
-
"wrong-hash",
|
|
254
|
-
);
|
|
243
|
+
const found = findPendingChallengeByHash("telegram", "wrong-hash");
|
|
255
244
|
expect(found).toBeNull();
|
|
256
245
|
});
|
|
257
246
|
|
|
258
247
|
test("findPendingChallengeByHash returns null for expired challenge", () => {
|
|
259
248
|
createChallenge({
|
|
260
249
|
id: "chal-1",
|
|
261
|
-
assistantId: "asst-1",
|
|
262
250
|
channel: "telegram",
|
|
263
251
|
challengeHash: "abc123hash",
|
|
264
252
|
expiresAt: Date.now() - 1000, // already expired
|
|
265
253
|
});
|
|
266
254
|
|
|
267
|
-
const found = findPendingChallengeByHash(
|
|
268
|
-
"asst-1",
|
|
269
|
-
"telegram",
|
|
270
|
-
"abc123hash",
|
|
271
|
-
);
|
|
255
|
+
const found = findPendingChallengeByHash("telegram", "abc123hash");
|
|
272
256
|
expect(found).toBeNull();
|
|
273
257
|
});
|
|
274
258
|
|
|
275
259
|
test("consumeChallenge marks challenge as consumed", () => {
|
|
276
260
|
createChallenge({
|
|
277
261
|
id: "chal-1",
|
|
278
|
-
assistantId: "asst-1",
|
|
279
262
|
channel: "telegram",
|
|
280
263
|
challengeHash: "abc123hash",
|
|
281
264
|
expiresAt: Date.now() + 600_000,
|
|
@@ -284,62 +267,40 @@ describe("verification challenge lifecycle", () => {
|
|
|
284
267
|
consumeChallenge("chal-1", "user-42", "chat-42");
|
|
285
268
|
|
|
286
269
|
// After consumption, findPendingChallengeByHash should return null
|
|
287
|
-
const found = findPendingChallengeByHash(
|
|
288
|
-
"asst-1",
|
|
289
|
-
"telegram",
|
|
290
|
-
"abc123hash",
|
|
291
|
-
);
|
|
270
|
+
const found = findPendingChallengeByHash("telegram", "abc123hash");
|
|
292
271
|
expect(found).toBeNull();
|
|
293
272
|
});
|
|
294
273
|
|
|
295
274
|
test("consumed challenge cannot be found again (replay prevention)", () => {
|
|
296
275
|
createChallenge({
|
|
297
276
|
id: "chal-1",
|
|
298
|
-
assistantId: "asst-1",
|
|
299
277
|
channel: "telegram",
|
|
300
278
|
challengeHash: "abc123hash",
|
|
301
279
|
expiresAt: Date.now() + 600_000,
|
|
302
280
|
});
|
|
303
281
|
|
|
304
282
|
// First consumption succeeds
|
|
305
|
-
const found1 = findPendingChallengeByHash(
|
|
306
|
-
"asst-1",
|
|
307
|
-
"telegram",
|
|
308
|
-
"abc123hash",
|
|
309
|
-
);
|
|
283
|
+
const found1 = findPendingChallengeByHash("telegram", "abc123hash");
|
|
310
284
|
expect(found1).not.toBeNull();
|
|
311
285
|
consumeChallenge("chal-1", "user-42", "chat-42");
|
|
312
286
|
|
|
313
287
|
// Second lookup returns null because challenge is consumed
|
|
314
|
-
const found2 = findPendingChallengeByHash(
|
|
315
|
-
"asst-1",
|
|
316
|
-
"telegram",
|
|
317
|
-
"abc123hash",
|
|
318
|
-
);
|
|
288
|
+
const found2 = findPendingChallengeByHash("telegram", "abc123hash");
|
|
319
289
|
expect(found2).toBeNull();
|
|
320
290
|
});
|
|
321
291
|
|
|
322
|
-
test("findPendingChallengeByHash scoped to
|
|
292
|
+
test("findPendingChallengeByHash scoped to channel", () => {
|
|
323
293
|
createChallenge({
|
|
324
294
|
id: "chal-1",
|
|
325
|
-
assistantId: "asst-1",
|
|
326
295
|
channel: "telegram",
|
|
327
296
|
challengeHash: "abc123hash",
|
|
328
297
|
expiresAt: Date.now() + 600_000,
|
|
329
298
|
});
|
|
330
299
|
|
|
331
|
-
// Different
|
|
332
|
-
expect(
|
|
333
|
-
|
|
334
|
-
).toBeNull();
|
|
335
|
-
// Different channel
|
|
336
|
-
expect(
|
|
337
|
-
findPendingChallengeByHash("asst-1", "slack", "abc123hash"),
|
|
338
|
-
).toBeNull();
|
|
339
|
-
// Correct match
|
|
340
|
-
expect(
|
|
341
|
-
findPendingChallengeByHash("asst-1", "telegram", "abc123hash"),
|
|
342
|
-
).not.toBeNull();
|
|
300
|
+
// Different channel — should not find
|
|
301
|
+
expect(findPendingChallengeByHash("slack", "abc123hash")).toBeNull();
|
|
302
|
+
// Correct channel — should find
|
|
303
|
+
expect(findPendingChallengeByHash("telegram", "abc123hash")).not.toBeNull();
|
|
343
304
|
});
|
|
344
305
|
});
|
|
345
306
|
|
|
@@ -353,7 +314,7 @@ describe("guardian service challenge validation", () => {
|
|
|
353
314
|
});
|
|
354
315
|
|
|
355
316
|
test("createVerificationChallenge returns a secret, verifyCommand, ttlSeconds, and instruction", () => {
|
|
356
|
-
const result = createVerificationChallenge("
|
|
317
|
+
const result = createVerificationChallenge("telegram");
|
|
357
318
|
|
|
358
319
|
expect(result.challengeId).toBeDefined();
|
|
359
320
|
expect(result.secret).toBeDefined();
|
|
@@ -368,24 +329,23 @@ describe("guardian service challenge validation", () => {
|
|
|
368
329
|
});
|
|
369
330
|
|
|
370
331
|
test("createVerificationChallenge produces a non-empty instruction for telegram channel", () => {
|
|
371
|
-
const result = createVerificationChallenge("
|
|
332
|
+
const result = createVerificationChallenge("telegram");
|
|
372
333
|
expect(result.instruction).toBeDefined();
|
|
373
334
|
expect(result.instruction.length).toBeGreaterThan(0);
|
|
374
335
|
expect(result.instruction).toContain(`the code: ${result.secret}`);
|
|
375
336
|
});
|
|
376
337
|
|
|
377
338
|
test("createVerificationChallenge produces a non-empty instruction for sms channel", () => {
|
|
378
|
-
const result = createVerificationChallenge("
|
|
339
|
+
const result = createVerificationChallenge("sms");
|
|
379
340
|
expect(result.instruction).toBeDefined();
|
|
380
341
|
expect(result.instruction.length).toBeGreaterThan(0);
|
|
381
342
|
expect(result.instruction).toContain(`the code: ${result.secret}`);
|
|
382
343
|
});
|
|
383
344
|
|
|
384
345
|
test("validateAndConsumeChallenge succeeds with correct secret", () => {
|
|
385
|
-
const { secret } = createVerificationChallenge("
|
|
346
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
386
347
|
|
|
387
348
|
const result = validateAndConsumeChallenge(
|
|
388
|
-
"asst-1",
|
|
389
349
|
"telegram",
|
|
390
350
|
secret,
|
|
391
351
|
"user-42",
|
|
@@ -399,25 +359,18 @@ describe("guardian service challenge validation", () => {
|
|
|
399
359
|
});
|
|
400
360
|
|
|
401
361
|
test("validateAndConsumeChallenge does not create a guardian binding (caller responsibility)", () => {
|
|
402
|
-
const { secret } = createVerificationChallenge("
|
|
362
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
403
363
|
|
|
404
|
-
validateAndConsumeChallenge(
|
|
405
|
-
"asst-1",
|
|
406
|
-
"telegram",
|
|
407
|
-
secret,
|
|
408
|
-
"user-42",
|
|
409
|
-
"chat-42",
|
|
410
|
-
);
|
|
364
|
+
validateAndConsumeChallenge("telegram", secret, "user-42", "chat-42");
|
|
411
365
|
|
|
412
366
|
const binding = getGuardianBinding("asst-1", "telegram");
|
|
413
367
|
expect(binding).toBeNull();
|
|
414
368
|
});
|
|
415
369
|
|
|
416
370
|
test("validateAndConsumeChallenge fails with wrong secret", () => {
|
|
417
|
-
createVerificationChallenge("
|
|
371
|
+
createVerificationChallenge("telegram");
|
|
418
372
|
|
|
419
373
|
const result = validateAndConsumeChallenge(
|
|
420
|
-
"asst-1",
|
|
421
374
|
"telegram",
|
|
422
375
|
"wrong-secret",
|
|
423
376
|
"user-42",
|
|
@@ -439,14 +392,12 @@ describe("guardian service challenge validation", () => {
|
|
|
439
392
|
const challengeHash = createHash("sha256").update(secret).digest("hex");
|
|
440
393
|
createChallenge({
|
|
441
394
|
id: "chal-expired",
|
|
442
|
-
assistantId: "asst-1",
|
|
443
395
|
channel: "telegram",
|
|
444
396
|
challengeHash,
|
|
445
397
|
expiresAt: Date.now() - 1000, // already expired
|
|
446
398
|
});
|
|
447
399
|
|
|
448
400
|
const result = validateAndConsumeChallenge(
|
|
449
|
-
"asst-1",
|
|
450
401
|
"telegram",
|
|
451
402
|
secret,
|
|
452
403
|
"user-42",
|
|
@@ -463,11 +414,10 @@ describe("guardian service challenge validation", () => {
|
|
|
463
414
|
});
|
|
464
415
|
|
|
465
416
|
test("consumed challenge cannot be reused", () => {
|
|
466
|
-
const { secret } = createVerificationChallenge("
|
|
417
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
467
418
|
|
|
468
419
|
// First use succeeds
|
|
469
420
|
const result1 = validateAndConsumeChallenge(
|
|
470
|
-
"asst-1",
|
|
471
421
|
"telegram",
|
|
472
422
|
secret,
|
|
473
423
|
"user-42",
|
|
@@ -477,7 +427,6 @@ describe("guardian service challenge validation", () => {
|
|
|
477
427
|
|
|
478
428
|
// Second use with same secret fails (replay prevention)
|
|
479
429
|
const result2 = validateAndConsumeChallenge(
|
|
480
|
-
"asst-1",
|
|
481
430
|
"telegram",
|
|
482
431
|
secret,
|
|
483
432
|
"user-99",
|
|
@@ -487,10 +436,9 @@ describe("guardian service challenge validation", () => {
|
|
|
487
436
|
});
|
|
488
437
|
|
|
489
438
|
test("validateAndConsumeChallenge succeeds with sms channel", () => {
|
|
490
|
-
const { secret } = createVerificationChallenge("
|
|
439
|
+
const { secret } = createVerificationChallenge("sms");
|
|
491
440
|
|
|
492
441
|
const result = validateAndConsumeChallenge(
|
|
493
|
-
"asst-1",
|
|
494
442
|
"sms",
|
|
495
443
|
secret,
|
|
496
444
|
"phone-user-1",
|
|
@@ -509,12 +457,11 @@ describe("guardian service challenge validation", () => {
|
|
|
509
457
|
});
|
|
510
458
|
|
|
511
459
|
test("sms and telegram guardian challenges are independent", () => {
|
|
512
|
-
const telegramChallenge = createVerificationChallenge("
|
|
513
|
-
const smsChallenge = createVerificationChallenge("
|
|
460
|
+
const telegramChallenge = createVerificationChallenge("telegram");
|
|
461
|
+
const smsChallenge = createVerificationChallenge("sms");
|
|
514
462
|
|
|
515
463
|
// Validate SMS challenge against telegram channel should fail
|
|
516
464
|
const crossResult = validateAndConsumeChallenge(
|
|
517
|
-
"asst-1",
|
|
518
465
|
"telegram",
|
|
519
466
|
smsChallenge.secret,
|
|
520
467
|
"user-1",
|
|
@@ -524,7 +471,6 @@ describe("guardian service challenge validation", () => {
|
|
|
524
471
|
|
|
525
472
|
// Validate SMS challenge against correct channel should succeed
|
|
526
473
|
const smsResult = validateAndConsumeChallenge(
|
|
527
|
-
"asst-1",
|
|
528
474
|
"sms",
|
|
529
475
|
smsChallenge.secret,
|
|
530
476
|
"user-1",
|
|
@@ -534,7 +480,6 @@ describe("guardian service challenge validation", () => {
|
|
|
534
480
|
|
|
535
481
|
// Telegram challenge should still be valid
|
|
536
482
|
const telegramResult = validateAndConsumeChallenge(
|
|
537
|
-
"asst-1",
|
|
538
483
|
"telegram",
|
|
539
484
|
telegramChallenge.secret,
|
|
540
485
|
"user-2",
|
|
@@ -546,7 +491,6 @@ describe("guardian service challenge validation", () => {
|
|
|
546
491
|
test("validateAndConsumeChallenge succeeds even with existing binding (conflict check is caller responsibility)", () => {
|
|
547
492
|
// Create initial guardian binding
|
|
548
493
|
createGuardianBinding({
|
|
549
|
-
assistantId: "asst-1",
|
|
550
494
|
channel: "telegram",
|
|
551
495
|
guardianExternalUserId: "old-user",
|
|
552
496
|
guardianPrincipalId: "old-user",
|
|
@@ -557,9 +501,8 @@ describe("guardian service challenge validation", () => {
|
|
|
557
501
|
expect(oldBinding).not.toBeNull();
|
|
558
502
|
expect(oldBinding!.guardianExternalUserId).toBe("old-user");
|
|
559
503
|
|
|
560
|
-
const { secret } = createVerificationChallenge("
|
|
504
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
561
505
|
const result = validateAndConsumeChallenge(
|
|
562
|
-
"asst-1",
|
|
563
506
|
"telegram",
|
|
564
507
|
secret,
|
|
565
508
|
"new-user",
|
|
@@ -586,7 +529,6 @@ describe("guardian identity check", () => {
|
|
|
586
529
|
|
|
587
530
|
test("isGuardian returns true for matching user", () => {
|
|
588
531
|
createGuardianBinding({
|
|
589
|
-
assistantId: "asst-1",
|
|
590
532
|
channel: "telegram",
|
|
591
533
|
guardianExternalUserId: "user-42",
|
|
592
534
|
guardianPrincipalId: "user-42",
|
|
@@ -598,7 +540,6 @@ describe("guardian identity check", () => {
|
|
|
598
540
|
|
|
599
541
|
test("isGuardian returns false for non-matching user", () => {
|
|
600
542
|
createGuardianBinding({
|
|
601
|
-
assistantId: "asst-1",
|
|
602
543
|
channel: "telegram",
|
|
603
544
|
guardianExternalUserId: "user-42",
|
|
604
545
|
guardianPrincipalId: "user-42",
|
|
@@ -614,7 +555,6 @@ describe("guardian identity check", () => {
|
|
|
614
555
|
|
|
615
556
|
test("isGuardian returns false after binding is revoked", () => {
|
|
616
557
|
createGuardianBinding({
|
|
617
|
-
assistantId: "asst-1",
|
|
618
558
|
channel: "telegram",
|
|
619
559
|
guardianExternalUserId: "user-42",
|
|
620
560
|
guardianPrincipalId: "user-42",
|
|
@@ -628,7 +568,6 @@ describe("guardian identity check", () => {
|
|
|
628
568
|
|
|
629
569
|
test("getGuardianBinding returns the active binding", () => {
|
|
630
570
|
createGuardianBinding({
|
|
631
|
-
assistantId: "asst-1",
|
|
632
571
|
channel: "telegram",
|
|
633
572
|
guardianExternalUserId: "user-42",
|
|
634
573
|
guardianPrincipalId: "user-42",
|
|
@@ -647,7 +586,6 @@ describe("guardian identity check", () => {
|
|
|
647
586
|
|
|
648
587
|
test("isGuardian works for sms channel", () => {
|
|
649
588
|
createGuardianBinding({
|
|
650
|
-
assistantId: "asst-1",
|
|
651
589
|
channel: "sms",
|
|
652
590
|
guardianExternalUserId: "phone-user-1",
|
|
653
591
|
guardianPrincipalId: "phone-user-1",
|
|
@@ -662,7 +600,6 @@ describe("guardian identity check", () => {
|
|
|
662
600
|
|
|
663
601
|
test("serviceRevokeBinding revokes the active binding", () => {
|
|
664
602
|
createGuardianBinding({
|
|
665
|
-
assistantId: "asst-1",
|
|
666
603
|
channel: "telegram",
|
|
667
604
|
guardianExternalUserId: "user-42",
|
|
668
605
|
guardianPrincipalId: "user-42",
|
|
@@ -934,13 +871,12 @@ describe("verification rate limiting store", () => {
|
|
|
934
871
|
});
|
|
935
872
|
|
|
936
873
|
test("getRateLimit returns null when no record exists", () => {
|
|
937
|
-
const rl = getRateLimit("
|
|
874
|
+
const rl = getRateLimit("telegram", "user-42", "chat-42");
|
|
938
875
|
expect(rl).toBeNull();
|
|
939
876
|
});
|
|
940
877
|
|
|
941
878
|
test("recordInvalidAttempt creates a new record on first failure", () => {
|
|
942
879
|
const rl = recordInvalidAttempt(
|
|
943
|
-
"asst-1",
|
|
944
880
|
"telegram",
|
|
945
881
|
"user-42",
|
|
946
882
|
"chat-42",
|
|
@@ -950,14 +886,13 @@ describe("verification rate limiting store", () => {
|
|
|
950
886
|
);
|
|
951
887
|
expect(rl.invalidAttempts).toBe(1);
|
|
952
888
|
expect(rl.lockedUntil).toBeNull();
|
|
953
|
-
|
|
889
|
+
// assistantId column has been removed; no longer asserted
|
|
954
890
|
expect(rl.channel).toBe("telegram");
|
|
955
891
|
expect(rl.actorExternalUserId).toBe("user-42");
|
|
956
892
|
});
|
|
957
893
|
|
|
958
894
|
test("recordInvalidAttempt increments counter on subsequent failures", () => {
|
|
959
895
|
recordInvalidAttempt(
|
|
960
|
-
"asst-1",
|
|
961
896
|
"telegram",
|
|
962
897
|
"user-42",
|
|
963
898
|
"chat-42",
|
|
@@ -966,7 +901,6 @@ describe("verification rate limiting store", () => {
|
|
|
966
901
|
1_800_000,
|
|
967
902
|
);
|
|
968
903
|
recordInvalidAttempt(
|
|
969
|
-
"asst-1",
|
|
970
904
|
"telegram",
|
|
971
905
|
"user-42",
|
|
972
906
|
"chat-42",
|
|
@@ -975,7 +909,6 @@ describe("verification rate limiting store", () => {
|
|
|
975
909
|
1_800_000,
|
|
976
910
|
);
|
|
977
911
|
const rl = recordInvalidAttempt(
|
|
978
|
-
"asst-1",
|
|
979
912
|
"telegram",
|
|
980
913
|
"user-42",
|
|
981
914
|
"chat-42",
|
|
@@ -990,7 +923,6 @@ describe("verification rate limiting store", () => {
|
|
|
990
923
|
test("recordInvalidAttempt sets lockedUntil when max attempts reached", () => {
|
|
991
924
|
for (let i = 0; i < 4; i++) {
|
|
992
925
|
recordInvalidAttempt(
|
|
993
|
-
"asst-1",
|
|
994
926
|
"telegram",
|
|
995
927
|
"user-42",
|
|
996
928
|
"chat-42",
|
|
@@ -1000,7 +932,6 @@ describe("verification rate limiting store", () => {
|
|
|
1000
932
|
);
|
|
1001
933
|
}
|
|
1002
934
|
const rl = recordInvalidAttempt(
|
|
1003
|
-
"asst-1",
|
|
1004
935
|
"telegram",
|
|
1005
936
|
"user-42",
|
|
1006
937
|
"chat-42",
|
|
@@ -1016,7 +947,6 @@ describe("verification rate limiting store", () => {
|
|
|
1016
947
|
test("resetRateLimit clears the counter and lockout", () => {
|
|
1017
948
|
for (let i = 0; i < 5; i++) {
|
|
1018
949
|
recordInvalidAttempt(
|
|
1019
|
-
"asst-1",
|
|
1020
950
|
"telegram",
|
|
1021
951
|
"user-42",
|
|
1022
952
|
"chat-42",
|
|
@@ -1025,13 +955,13 @@ describe("verification rate limiting store", () => {
|
|
|
1025
955
|
1_800_000,
|
|
1026
956
|
);
|
|
1027
957
|
}
|
|
1028
|
-
const locked = getRateLimit("
|
|
958
|
+
const locked = getRateLimit("telegram", "user-42", "chat-42");
|
|
1029
959
|
expect(locked).not.toBeNull();
|
|
1030
960
|
expect(locked!.lockedUntil).not.toBeNull();
|
|
1031
961
|
|
|
1032
|
-
resetRateLimit("
|
|
962
|
+
resetRateLimit("telegram", "user-42", "chat-42");
|
|
1033
963
|
|
|
1034
|
-
const after = getRateLimit("
|
|
964
|
+
const after = getRateLimit("telegram", "user-42", "chat-42");
|
|
1035
965
|
expect(after).not.toBeNull();
|
|
1036
966
|
expect(after!.invalidAttempts).toBe(0);
|
|
1037
967
|
expect(after!.lockedUntil).toBeNull();
|
|
@@ -1039,7 +969,6 @@ describe("verification rate limiting store", () => {
|
|
|
1039
969
|
|
|
1040
970
|
test("rate limits are scoped per actor and channel", () => {
|
|
1041
971
|
recordInvalidAttempt(
|
|
1042
|
-
"asst-1",
|
|
1043
972
|
"telegram",
|
|
1044
973
|
"user-42",
|
|
1045
974
|
"chat-42",
|
|
@@ -1048,7 +977,6 @@ describe("verification rate limiting store", () => {
|
|
|
1048
977
|
1_800_000,
|
|
1049
978
|
);
|
|
1050
979
|
recordInvalidAttempt(
|
|
1051
|
-
"asst-1",
|
|
1052
980
|
"telegram",
|
|
1053
981
|
"user-99",
|
|
1054
982
|
"chat-99",
|
|
@@ -1057,9 +985,9 @@ describe("verification rate limiting store", () => {
|
|
|
1057
985
|
1_800_000,
|
|
1058
986
|
);
|
|
1059
987
|
|
|
1060
|
-
const rl42 = getRateLimit("
|
|
1061
|
-
const rl99 = getRateLimit("
|
|
1062
|
-
const rlSms = getRateLimit("
|
|
988
|
+
const rl42 = getRateLimit("telegram", "user-42", "chat-42");
|
|
989
|
+
const rl99 = getRateLimit("telegram", "user-99", "chat-99");
|
|
990
|
+
const rlSms = getRateLimit("sms", "user-42", "chat-42");
|
|
1063
991
|
|
|
1064
992
|
expect(rl42).not.toBeNull();
|
|
1065
993
|
expect(rl42!.invalidAttempts).toBe(1);
|
|
@@ -1080,12 +1008,11 @@ describe("guardian service rate limiting", () => {
|
|
|
1080
1008
|
|
|
1081
1009
|
test("repeated invalid submissions hit rate limit", () => {
|
|
1082
1010
|
// Create a valid challenge so there is a pending challenge
|
|
1083
|
-
createVerificationChallenge("
|
|
1011
|
+
createVerificationChallenge("telegram");
|
|
1084
1012
|
|
|
1085
1013
|
// Submit wrong codes repeatedly
|
|
1086
1014
|
for (let i = 0; i < 5; i++) {
|
|
1087
1015
|
const result = validateAndConsumeChallenge(
|
|
1088
|
-
"asst-1",
|
|
1089
1016
|
"telegram",
|
|
1090
1017
|
`wrong-secret-${i}`,
|
|
1091
1018
|
"user-42",
|
|
@@ -1096,7 +1023,6 @@ describe("guardian service rate limiting", () => {
|
|
|
1096
1023
|
|
|
1097
1024
|
// The 6th attempt should be rate-limited even without a new challenge
|
|
1098
1025
|
const result = validateAndConsumeChallenge(
|
|
1099
|
-
"asst-1",
|
|
1100
1026
|
"telegram",
|
|
1101
1027
|
"another-wrong",
|
|
1102
1028
|
"user-42",
|
|
@@ -1110,40 +1036,21 @@ describe("guardian service rate limiting", () => {
|
|
|
1110
1036
|
);
|
|
1111
1037
|
|
|
1112
1038
|
// Verify the rate limit record
|
|
1113
|
-
const rl = getRateLimit("
|
|
1039
|
+
const rl = getRateLimit("telegram", "user-42", "chat-42");
|
|
1114
1040
|
expect(rl).not.toBeNull();
|
|
1115
1041
|
expect(rl!.lockedUntil).not.toBeNull();
|
|
1116
1042
|
});
|
|
1117
1043
|
|
|
1118
1044
|
test("valid challenge still succeeds when under threshold", () => {
|
|
1119
1045
|
// Record a couple invalid attempts
|
|
1120
|
-
const { secret: _secret } = createVerificationChallenge(
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
);
|
|
1124
|
-
validateAndConsumeChallenge(
|
|
1125
|
-
"asst-1",
|
|
1126
|
-
"telegram",
|
|
1127
|
-
"wrong-1",
|
|
1128
|
-
"user-42",
|
|
1129
|
-
"chat-42",
|
|
1130
|
-
);
|
|
1131
|
-
validateAndConsumeChallenge(
|
|
1132
|
-
"asst-1",
|
|
1133
|
-
"telegram",
|
|
1134
|
-
"wrong-2",
|
|
1135
|
-
"user-42",
|
|
1136
|
-
"chat-42",
|
|
1137
|
-
);
|
|
1046
|
+
const { secret: _secret } = createVerificationChallenge("telegram");
|
|
1047
|
+
validateAndConsumeChallenge("telegram", "wrong-1", "user-42", "chat-42");
|
|
1048
|
+
validateAndConsumeChallenge("telegram", "wrong-2", "user-42", "chat-42");
|
|
1138
1049
|
|
|
1139
1050
|
// Valid attempt should still succeed (under the 5-attempt threshold)
|
|
1140
1051
|
// Need a new challenge since the old one is still pending but the secret was never consumed
|
|
1141
|
-
const { secret: secret2 } = createVerificationChallenge(
|
|
1142
|
-
"asst-1",
|
|
1143
|
-
"telegram",
|
|
1144
|
-
);
|
|
1052
|
+
const { secret: secret2 } = createVerificationChallenge("telegram");
|
|
1145
1053
|
const result = validateAndConsumeChallenge(
|
|
1146
|
-
"asst-1",
|
|
1147
1054
|
"telegram",
|
|
1148
1055
|
secret2,
|
|
1149
1056
|
"user-42",
|
|
@@ -1152,18 +1059,17 @@ describe("guardian service rate limiting", () => {
|
|
|
1152
1059
|
expect(result.success).toBe(true);
|
|
1153
1060
|
|
|
1154
1061
|
// Rate limit should be reset after success
|
|
1155
|
-
const rl = getRateLimit("
|
|
1062
|
+
const rl = getRateLimit("telegram", "user-42", "chat-42");
|
|
1156
1063
|
expect(rl).not.toBeNull();
|
|
1157
1064
|
expect(rl!.invalidAttempts).toBe(0);
|
|
1158
1065
|
expect(rl!.lockedUntil).toBeNull();
|
|
1159
1066
|
});
|
|
1160
1067
|
|
|
1161
1068
|
test("rate-limit uses generic failure message (no oracle leakage)", () => {
|
|
1162
|
-
createVerificationChallenge("
|
|
1069
|
+
createVerificationChallenge("telegram");
|
|
1163
1070
|
|
|
1164
1071
|
// Capture a normal invalid-code failure response
|
|
1165
1072
|
const normalFailure = validateAndConsumeChallenge(
|
|
1166
|
-
"asst-1",
|
|
1167
1073
|
"telegram",
|
|
1168
1074
|
"wrong-first",
|
|
1169
1075
|
"user-42",
|
|
@@ -1175,7 +1081,6 @@ describe("guardian service rate limiting", () => {
|
|
|
1175
1081
|
// Trigger rate limit (4 more attempts to reach 5 total)
|
|
1176
1082
|
for (let i = 0; i < 4; i++) {
|
|
1177
1083
|
validateAndConsumeChallenge(
|
|
1178
|
-
"asst-1",
|
|
1179
1084
|
"telegram",
|
|
1180
1085
|
`wrong-${i}`,
|
|
1181
1086
|
"user-42",
|
|
@@ -1184,13 +1089,12 @@ describe("guardian service rate limiting", () => {
|
|
|
1184
1089
|
}
|
|
1185
1090
|
|
|
1186
1091
|
// Verify lockout is actually active before testing the rate-limited response
|
|
1187
|
-
const rl = getRateLimit("
|
|
1092
|
+
const rl = getRateLimit("telegram", "user-42", "chat-42");
|
|
1188
1093
|
expect(rl).not.toBeNull();
|
|
1189
1094
|
expect(rl!.lockedUntil).toBeGreaterThan(Date.now());
|
|
1190
1095
|
|
|
1191
1096
|
// The rate-limited response should be indistinguishable from normal failure
|
|
1192
1097
|
const rateLimitedResult = validateAndConsumeChallenge(
|
|
1193
|
-
"asst-1",
|
|
1194
1098
|
"telegram",
|
|
1195
1099
|
"anything",
|
|
1196
1100
|
"user-42",
|
|
@@ -1209,10 +1113,9 @@ describe("guardian service rate limiting", () => {
|
|
|
1209
1113
|
|
|
1210
1114
|
test("rate limit does not affect different actors", () => {
|
|
1211
1115
|
// Rate-limit user-42
|
|
1212
|
-
createVerificationChallenge("
|
|
1116
|
+
createVerificationChallenge("telegram");
|
|
1213
1117
|
for (let i = 0; i < 5; i++) {
|
|
1214
1118
|
validateAndConsumeChallenge(
|
|
1215
|
-
"asst-1",
|
|
1216
1119
|
"telegram",
|
|
1217
1120
|
`wrong-${i}`,
|
|
1218
1121
|
"user-42",
|
|
@@ -1221,9 +1124,8 @@ describe("guardian service rate limiting", () => {
|
|
|
1221
1124
|
}
|
|
1222
1125
|
|
|
1223
1126
|
// user-99 should still be able to verify
|
|
1224
|
-
const { secret } = createVerificationChallenge("
|
|
1127
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
1225
1128
|
const result = validateAndConsumeChallenge(
|
|
1226
|
-
"asst-1",
|
|
1227
1129
|
"telegram",
|
|
1228
1130
|
secret,
|
|
1229
1131
|
"user-99",
|
|
@@ -1245,7 +1147,6 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1245
1147
|
test("isGuardian resolves independently per channel", () => {
|
|
1246
1148
|
// Create guardian binding on telegram
|
|
1247
1149
|
createGuardianBinding({
|
|
1248
|
-
assistantId: "self",
|
|
1249
1150
|
channel: "telegram",
|
|
1250
1151
|
guardianExternalUserId: "user-alpha",
|
|
1251
1152
|
guardianPrincipalId: "user-alpha",
|
|
@@ -1253,7 +1154,6 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1253
1154
|
});
|
|
1254
1155
|
// Create guardian binding on sms with a different user
|
|
1255
1156
|
createGuardianBinding({
|
|
1256
|
-
assistantId: "self",
|
|
1257
1157
|
channel: "sms",
|
|
1258
1158
|
guardianExternalUserId: "user-beta",
|
|
1259
1159
|
guardianPrincipalId: "user-beta",
|
|
@@ -1271,14 +1171,12 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1271
1171
|
|
|
1272
1172
|
test("getGuardianBinding returns different bindings for different channels", () => {
|
|
1273
1173
|
createGuardianBinding({
|
|
1274
|
-
assistantId: "self",
|
|
1275
1174
|
channel: "telegram",
|
|
1276
1175
|
guardianExternalUserId: "user-alpha",
|
|
1277
1176
|
guardianPrincipalId: "user-alpha",
|
|
1278
1177
|
guardianDeliveryChatId: "chat-alpha",
|
|
1279
1178
|
});
|
|
1280
1179
|
createGuardianBinding({
|
|
1281
|
-
assistantId: "self",
|
|
1282
1180
|
channel: "sms",
|
|
1283
1181
|
guardianExternalUserId: "user-beta",
|
|
1284
1182
|
guardianPrincipalId: "user-beta",
|
|
@@ -1296,14 +1194,12 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1296
1194
|
|
|
1297
1195
|
test("revoking binding for one channel does not affect another", () => {
|
|
1298
1196
|
createGuardianBinding({
|
|
1299
|
-
assistantId: "self",
|
|
1300
1197
|
channel: "telegram",
|
|
1301
1198
|
guardianExternalUserId: "user-alpha",
|
|
1302
1199
|
guardianPrincipalId: "user-alpha",
|
|
1303
1200
|
guardianDeliveryChatId: "chat-alpha",
|
|
1304
1201
|
});
|
|
1305
1202
|
createGuardianBinding({
|
|
1306
|
-
assistantId: "self",
|
|
1307
1203
|
channel: "sms",
|
|
1308
1204
|
guardianExternalUserId: "user-beta",
|
|
1309
1205
|
guardianPrincipalId: "user-beta",
|
|
@@ -1318,16 +1214,12 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1318
1214
|
|
|
1319
1215
|
test("validateAndConsumeChallenge scoped to channel", () => {
|
|
1320
1216
|
// Create challenge on telegram
|
|
1321
|
-
const { secret: secretTelegram } = createVerificationChallenge(
|
|
1322
|
-
"self",
|
|
1323
|
-
"telegram",
|
|
1324
|
-
);
|
|
1217
|
+
const { secret: secretTelegram } = createVerificationChallenge("telegram");
|
|
1325
1218
|
// Create challenge on sms
|
|
1326
|
-
const { secret: secretSms } = createVerificationChallenge("
|
|
1219
|
+
const { secret: secretSms } = createVerificationChallenge("sms");
|
|
1327
1220
|
|
|
1328
1221
|
// Attempting to consume telegram challenge on sms should fail
|
|
1329
1222
|
const crossResult = validateAndConsumeChallenge(
|
|
1330
|
-
"self",
|
|
1331
1223
|
"sms",
|
|
1332
1224
|
secretTelegram,
|
|
1333
1225
|
"user-1",
|
|
@@ -1337,7 +1229,6 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1337
1229
|
|
|
1338
1230
|
// Consuming with correct channel should succeed
|
|
1339
1231
|
const resultTelegram = validateAndConsumeChallenge(
|
|
1340
|
-
"self",
|
|
1341
1232
|
"telegram",
|
|
1342
1233
|
secretTelegram,
|
|
1343
1234
|
"user-1",
|
|
@@ -1346,7 +1237,6 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
1346
1237
|
expect(resultTelegram.success).toBe(true);
|
|
1347
1238
|
|
|
1348
1239
|
const resultSms = validateAndConsumeChallenge(
|
|
1349
|
-
"self",
|
|
1350
1240
|
"sms",
|
|
1351
1241
|
secretSms,
|
|
1352
1242
|
"user-2",
|
|
@@ -1370,8 +1260,8 @@ describe("assistant-scoped approval request lookups", () => {
|
|
|
1370
1260
|
resetTables();
|
|
1371
1261
|
});
|
|
1372
1262
|
|
|
1373
|
-
test("createApprovalRequest
|
|
1374
|
-
const
|
|
1263
|
+
test("createApprovalRequest no longer exposes assistantId on the returned interface", () => {
|
|
1264
|
+
const req = createApprovalRequest({
|
|
1375
1265
|
runId: "run-1",
|
|
1376
1266
|
requestId: "req-1",
|
|
1377
1267
|
conversationId: "conv-1",
|
|
@@ -1383,30 +1273,16 @@ describe("assistant-scoped approval request lookups", () => {
|
|
|
1383
1273
|
toolName: "shell",
|
|
1384
1274
|
expiresAt: Date.now() + 300_000,
|
|
1385
1275
|
});
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
runId: "run-2",
|
|
1390
|
-
requestId: "req-2",
|
|
1391
|
-
conversationId: "conv-2",
|
|
1392
|
-
assistantId: "asst-A",
|
|
1393
|
-
channel: "telegram",
|
|
1394
|
-
requesterExternalUserId: "user-99",
|
|
1395
|
-
requesterChatId: "chat-99",
|
|
1396
|
-
guardianExternalUserId: "user-42",
|
|
1397
|
-
guardianChatId: "chat-42",
|
|
1398
|
-
toolName: "browser",
|
|
1399
|
-
expiresAt: Date.now() + 300_000,
|
|
1400
|
-
});
|
|
1401
|
-
expect(reqWithId.assistantId).toBe("asst-A");
|
|
1276
|
+
// assistantId is no longer on the public interface
|
|
1277
|
+
expect(req.id).toBeDefined();
|
|
1278
|
+
expect(req.toolName).toBe("shell");
|
|
1402
1279
|
});
|
|
1403
1280
|
|
|
1404
|
-
test("approval requests from different
|
|
1281
|
+
test("approval requests from different conversations are independent", () => {
|
|
1405
1282
|
createApprovalRequest({
|
|
1406
1283
|
runId: "run-A",
|
|
1407
1284
|
requestId: "req-A",
|
|
1408
1285
|
conversationId: "conv-A",
|
|
1409
|
-
assistantId: "asst-A",
|
|
1410
1286
|
channel: "telegram",
|
|
1411
1287
|
requesterExternalUserId: "user-99",
|
|
1412
1288
|
requesterChatId: "chat-99",
|
|
@@ -1419,7 +1295,6 @@ describe("assistant-scoped approval request lookups", () => {
|
|
|
1419
1295
|
runId: "run-B",
|
|
1420
1296
|
requestId: "req-B",
|
|
1421
1297
|
conversationId: "conv-B",
|
|
1422
|
-
assistantId: "asst-B",
|
|
1423
1298
|
channel: "telegram",
|
|
1424
1299
|
requesterExternalUserId: "user-88",
|
|
1425
1300
|
requesterChatId: "chat-88",
|
|
@@ -1433,8 +1308,6 @@ describe("assistant-scoped approval request lookups", () => {
|
|
|
1433
1308
|
const foundB = getPendingApprovalForRequest("req-B");
|
|
1434
1309
|
expect(foundA).not.toBeNull();
|
|
1435
1310
|
expect(foundB).not.toBeNull();
|
|
1436
|
-
expect(foundA!.assistantId).toBe("asst-A");
|
|
1437
|
-
expect(foundB!.assistantId).toBe("asst-B");
|
|
1438
1311
|
expect(foundA!.toolName).toBe("shell");
|
|
1439
1312
|
expect(foundB!.toolName).toBe("browser");
|
|
1440
1313
|
});
|
|
@@ -1524,7 +1397,6 @@ describe("IPC handler channel-aware guardian status", () => {
|
|
|
1524
1397
|
|
|
1525
1398
|
test("status action returns guardianDeliveryChatId when bound", () => {
|
|
1526
1399
|
createGuardianBinding({
|
|
1527
|
-
assistantId: "self",
|
|
1528
1400
|
channel: "telegram",
|
|
1529
1401
|
guardianExternalUserId: "user-42",
|
|
1530
1402
|
guardianPrincipalId: "user-42",
|
|
@@ -1552,7 +1424,6 @@ describe("IPC handler channel-aware guardian status", () => {
|
|
|
1552
1424
|
|
|
1553
1425
|
test("status action returns guardian username/displayName from binding metadata", () => {
|
|
1554
1426
|
createGuardianBinding({
|
|
1555
|
-
assistantId: "self",
|
|
1556
1427
|
channel: "telegram",
|
|
1557
1428
|
guardianExternalUserId: "user-43",
|
|
1558
1429
|
guardianPrincipalId: "user-43",
|
|
@@ -1650,7 +1521,7 @@ describe("IPC handler channel-aware guardian status", () => {
|
|
|
1650
1521
|
});
|
|
1651
1522
|
|
|
1652
1523
|
test("status action includes hasPendingChallenge when challenge exists", () => {
|
|
1653
|
-
createVerificationChallenge("
|
|
1524
|
+
createVerificationChallenge("voice");
|
|
1654
1525
|
|
|
1655
1526
|
const { ctx, lastResponse } = createMockCtx();
|
|
1656
1527
|
const msg: GuardianVerificationRequest = {
|
|
@@ -1694,7 +1565,7 @@ describe("voice guardian challenge generation", () => {
|
|
|
1694
1565
|
});
|
|
1695
1566
|
|
|
1696
1567
|
test("createVerificationChallenge for voice returns a high-entropy hex secret", () => {
|
|
1697
|
-
const result = createVerificationChallenge("
|
|
1568
|
+
const result = createVerificationChallenge("voice");
|
|
1698
1569
|
|
|
1699
1570
|
expect(result.challengeId).toBeDefined();
|
|
1700
1571
|
expect(result.secret).toBeDefined();
|
|
@@ -1703,20 +1574,20 @@ describe("voice guardian challenge generation", () => {
|
|
|
1703
1574
|
});
|
|
1704
1575
|
|
|
1705
1576
|
test("createVerificationChallenge for non-voice returns high-entropy hex secret", () => {
|
|
1706
|
-
const result = createVerificationChallenge("
|
|
1577
|
+
const result = createVerificationChallenge("telegram");
|
|
1707
1578
|
|
|
1708
1579
|
expect(result.secret.length).toBe(64);
|
|
1709
1580
|
expect(result.secret).toMatch(/^[0-9a-f]{64}$/);
|
|
1710
1581
|
});
|
|
1711
1582
|
|
|
1712
1583
|
test("voice challenge verifyCommand contains the hex secret", () => {
|
|
1713
|
-
const result = createVerificationChallenge("
|
|
1584
|
+
const result = createVerificationChallenge("voice");
|
|
1714
1585
|
|
|
1715
1586
|
expect(result.verifyCommand).toBe(result.secret);
|
|
1716
1587
|
});
|
|
1717
1588
|
|
|
1718
1589
|
test("voice challenge instruction contains voice-specific copy", () => {
|
|
1719
|
-
const result = createVerificationChallenge("
|
|
1590
|
+
const result = createVerificationChallenge("voice");
|
|
1720
1591
|
|
|
1721
1592
|
// Inbound challenges use high-entropy hex, so the voice template says
|
|
1722
1593
|
// "enter the code" rather than "six-digit code".
|
|
@@ -1725,8 +1596,8 @@ describe("voice guardian challenge generation", () => {
|
|
|
1725
1596
|
});
|
|
1726
1597
|
|
|
1727
1598
|
test("voice challenge secrets are different across calls", () => {
|
|
1728
|
-
const result1 = createVerificationChallenge("
|
|
1729
|
-
const result2 = createVerificationChallenge("
|
|
1599
|
+
const result1 = createVerificationChallenge("voice");
|
|
1600
|
+
const result2 = createVerificationChallenge("voice");
|
|
1730
1601
|
|
|
1731
1602
|
// High-entropy hex secrets: collision probability is negligible
|
|
1732
1603
|
expect(result1.secret).toMatch(/^[0-9a-f]{64}$/);
|
|
@@ -1734,7 +1605,7 @@ describe("voice guardian challenge generation", () => {
|
|
|
1734
1605
|
});
|
|
1735
1606
|
|
|
1736
1607
|
test("voice ttlSeconds is 600 (10 minutes)", () => {
|
|
1737
|
-
const result = createVerificationChallenge("
|
|
1608
|
+
const result = createVerificationChallenge("voice");
|
|
1738
1609
|
expect(result.ttlSeconds).toBe(600);
|
|
1739
1610
|
});
|
|
1740
1611
|
});
|
|
@@ -1749,10 +1620,9 @@ describe("voice guardian challenge validation", () => {
|
|
|
1749
1620
|
});
|
|
1750
1621
|
|
|
1751
1622
|
test("validateAndConsumeChallenge succeeds with correct voice secret", () => {
|
|
1752
|
-
const { secret } = createVerificationChallenge("
|
|
1623
|
+
const { secret } = createVerificationChallenge("voice");
|
|
1753
1624
|
|
|
1754
1625
|
const result = validateAndConsumeChallenge(
|
|
1755
|
-
"asst-1",
|
|
1756
1626
|
"voice",
|
|
1757
1627
|
secret,
|
|
1758
1628
|
"voice-user-1",
|
|
@@ -1766,10 +1636,9 @@ describe("voice guardian challenge validation", () => {
|
|
|
1766
1636
|
});
|
|
1767
1637
|
|
|
1768
1638
|
test("validateAndConsumeChallenge does not create a guardian binding for voice (caller responsibility)", () => {
|
|
1769
|
-
const { secret } = createVerificationChallenge("
|
|
1639
|
+
const { secret } = createVerificationChallenge("voice");
|
|
1770
1640
|
|
|
1771
1641
|
validateAndConsumeChallenge(
|
|
1772
|
-
"asst-1",
|
|
1773
1642
|
"voice",
|
|
1774
1643
|
secret,
|
|
1775
1644
|
"voice-user-1",
|
|
@@ -1781,10 +1650,9 @@ describe("voice guardian challenge validation", () => {
|
|
|
1781
1650
|
});
|
|
1782
1651
|
|
|
1783
1652
|
test("validateAndConsumeChallenge fails with wrong voice secret", () => {
|
|
1784
|
-
createVerificationChallenge("
|
|
1653
|
+
createVerificationChallenge("voice");
|
|
1785
1654
|
|
|
1786
1655
|
const result = validateAndConsumeChallenge(
|
|
1787
|
-
"asst-1",
|
|
1788
1656
|
"voice",
|
|
1789
1657
|
"000000",
|
|
1790
1658
|
"voice-user-1",
|
|
@@ -1800,12 +1668,11 @@ describe("voice guardian challenge validation", () => {
|
|
|
1800
1668
|
});
|
|
1801
1669
|
|
|
1802
1670
|
test("voice and telegram guardian challenges are independent", () => {
|
|
1803
|
-
const voiceChallenge = createVerificationChallenge("
|
|
1804
|
-
const telegramChallenge = createVerificationChallenge("
|
|
1671
|
+
const voiceChallenge = createVerificationChallenge("voice");
|
|
1672
|
+
const telegramChallenge = createVerificationChallenge("telegram");
|
|
1805
1673
|
|
|
1806
1674
|
// Voice secret against telegram channel should fail
|
|
1807
1675
|
const crossResult = validateAndConsumeChallenge(
|
|
1808
|
-
"asst-1",
|
|
1809
1676
|
"telegram",
|
|
1810
1677
|
voiceChallenge.secret,
|
|
1811
1678
|
"user-1",
|
|
@@ -1815,7 +1682,6 @@ describe("voice guardian challenge validation", () => {
|
|
|
1815
1682
|
|
|
1816
1683
|
// Voice secret against correct channel should succeed
|
|
1817
1684
|
const voiceResult = validateAndConsumeChallenge(
|
|
1818
|
-
"asst-1",
|
|
1819
1685
|
"voice",
|
|
1820
1686
|
voiceChallenge.secret,
|
|
1821
1687
|
"voice-user-1",
|
|
@@ -1825,7 +1691,6 @@ describe("voice guardian challenge validation", () => {
|
|
|
1825
1691
|
|
|
1826
1692
|
// Telegram challenge should still be valid
|
|
1827
1693
|
const telegramResult = validateAndConsumeChallenge(
|
|
1828
|
-
"asst-1",
|
|
1829
1694
|
"telegram",
|
|
1830
1695
|
telegramChallenge.secret,
|
|
1831
1696
|
"user-2",
|
|
@@ -1835,10 +1700,9 @@ describe("voice guardian challenge validation", () => {
|
|
|
1835
1700
|
});
|
|
1836
1701
|
|
|
1837
1702
|
test("consumed voice challenge cannot be reused", () => {
|
|
1838
|
-
const { secret } = createVerificationChallenge("
|
|
1703
|
+
const { secret } = createVerificationChallenge("voice");
|
|
1839
1704
|
|
|
1840
1705
|
const result1 = validateAndConsumeChallenge(
|
|
1841
|
-
"asst-1",
|
|
1842
1706
|
"voice",
|
|
1843
1707
|
secret,
|
|
1844
1708
|
"voice-user-1",
|
|
@@ -1847,7 +1711,6 @@ describe("voice guardian challenge validation", () => {
|
|
|
1847
1711
|
expect(result1.success).toBe(true);
|
|
1848
1712
|
|
|
1849
1713
|
const result2 = validateAndConsumeChallenge(
|
|
1850
|
-
"asst-1",
|
|
1851
1714
|
"voice",
|
|
1852
1715
|
secret,
|
|
1853
1716
|
"voice-user-2",
|
|
@@ -1858,7 +1721,6 @@ describe("voice guardian challenge validation", () => {
|
|
|
1858
1721
|
|
|
1859
1722
|
test("validateAndConsumeChallenge succeeds even with existing voice binding (conflict check is caller responsibility)", () => {
|
|
1860
1723
|
createGuardianBinding({
|
|
1861
|
-
assistantId: "asst-1",
|
|
1862
1724
|
channel: "voice",
|
|
1863
1725
|
guardianExternalUserId: "old-voice-user",
|
|
1864
1726
|
guardianPrincipalId: "old-voice-user",
|
|
@@ -1869,9 +1731,8 @@ describe("voice guardian challenge validation", () => {
|
|
|
1869
1731
|
expect(oldBinding).not.toBeNull();
|
|
1870
1732
|
expect(oldBinding!.guardianExternalUserId).toBe("old-voice-user");
|
|
1871
1733
|
|
|
1872
|
-
const { secret } = createVerificationChallenge("
|
|
1734
|
+
const { secret } = createVerificationChallenge("voice");
|
|
1873
1735
|
const result = validateAndConsumeChallenge(
|
|
1874
|
-
"asst-1",
|
|
1875
1736
|
"voice",
|
|
1876
1737
|
secret,
|
|
1877
1738
|
"new-voice-user",
|
|
@@ -1899,7 +1760,6 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1899
1760
|
|
|
1900
1761
|
test("isGuardian works for voice channel", () => {
|
|
1901
1762
|
createGuardianBinding({
|
|
1902
|
-
assistantId: "asst-1",
|
|
1903
1763
|
channel: "voice",
|
|
1904
1764
|
guardianExternalUserId: "voice-user-1",
|
|
1905
1765
|
guardianPrincipalId: "voice-user-1",
|
|
@@ -1914,7 +1774,6 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1914
1774
|
|
|
1915
1775
|
test("getGuardianBinding returns voice binding", () => {
|
|
1916
1776
|
createGuardianBinding({
|
|
1917
|
-
assistantId: "asst-1",
|
|
1918
1777
|
channel: "voice",
|
|
1919
1778
|
guardianExternalUserId: "voice-user-1",
|
|
1920
1779
|
guardianPrincipalId: "voice-user-1",
|
|
@@ -1929,7 +1788,6 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1929
1788
|
|
|
1930
1789
|
test("revokeBinding clears active voice guardian binding", () => {
|
|
1931
1790
|
createGuardianBinding({
|
|
1932
|
-
assistantId: "asst-1",
|
|
1933
1791
|
channel: "voice",
|
|
1934
1792
|
guardianExternalUserId: "voice-user-1",
|
|
1935
1793
|
guardianPrincipalId: "voice-user-1",
|
|
@@ -1943,14 +1801,12 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1943
1801
|
|
|
1944
1802
|
test("revokeBinding for voice does not affect telegram binding", () => {
|
|
1945
1803
|
createGuardianBinding({
|
|
1946
|
-
assistantId: "asst-1",
|
|
1947
1804
|
channel: "voice",
|
|
1948
1805
|
guardianExternalUserId: "voice-user-1",
|
|
1949
1806
|
guardianPrincipalId: "voice-user-1",
|
|
1950
1807
|
guardianDeliveryChatId: "voice-chat-1",
|
|
1951
1808
|
});
|
|
1952
1809
|
createGuardianBinding({
|
|
1953
|
-
assistantId: "asst-1",
|
|
1954
1810
|
channel: "telegram",
|
|
1955
1811
|
guardianExternalUserId: "tg-user-1",
|
|
1956
1812
|
guardianPrincipalId: "tg-user-1",
|
|
@@ -1974,11 +1830,10 @@ describe("voice guardian rate limiting", () => {
|
|
|
1974
1830
|
});
|
|
1975
1831
|
|
|
1976
1832
|
test("repeated invalid voice submissions hit rate limit", () => {
|
|
1977
|
-
createVerificationChallenge("
|
|
1833
|
+
createVerificationChallenge("voice");
|
|
1978
1834
|
|
|
1979
1835
|
for (let i = 0; i < 5; i++) {
|
|
1980
1836
|
const result = validateAndConsumeChallenge(
|
|
1981
|
-
"asst-1",
|
|
1982
1837
|
"voice",
|
|
1983
1838
|
`${100000 + i}`,
|
|
1984
1839
|
"voice-user-1",
|
|
@@ -1989,7 +1844,6 @@ describe("voice guardian rate limiting", () => {
|
|
|
1989
1844
|
|
|
1990
1845
|
// The 6th attempt should be rate-limited
|
|
1991
1846
|
const result = validateAndConsumeChallenge(
|
|
1992
|
-
"asst-1",
|
|
1993
1847
|
"voice",
|
|
1994
1848
|
"999999",
|
|
1995
1849
|
"voice-user-1",
|
|
@@ -1997,43 +1851,35 @@ describe("voice guardian rate limiting", () => {
|
|
|
1997
1851
|
);
|
|
1998
1852
|
expect(result.success).toBe(false);
|
|
1999
1853
|
|
|
2000
|
-
const rl = getRateLimit("
|
|
1854
|
+
const rl = getRateLimit("voice", "voice-user-1", "voice-chat-1");
|
|
2001
1855
|
expect(rl).not.toBeNull();
|
|
2002
1856
|
expect(rl!.lockedUntil).not.toBeNull();
|
|
2003
1857
|
});
|
|
2004
1858
|
|
|
2005
1859
|
test("voice rate limit does not affect telegram rate limit", () => {
|
|
2006
|
-
createVerificationChallenge("
|
|
1860
|
+
createVerificationChallenge("voice");
|
|
2007
1861
|
for (let i = 0; i < 5; i++) {
|
|
2008
|
-
validateAndConsumeChallenge(
|
|
2009
|
-
"asst-1",
|
|
2010
|
-
"voice",
|
|
2011
|
-
`${100000 + i}`,
|
|
2012
|
-
"user-1",
|
|
2013
|
-
"chat-1",
|
|
2014
|
-
);
|
|
1862
|
+
validateAndConsumeChallenge("voice", `${100000 + i}`, "user-1", "chat-1");
|
|
2015
1863
|
}
|
|
2016
1864
|
|
|
2017
|
-
const voiceRl = getRateLimit("
|
|
1865
|
+
const voiceRl = getRateLimit("voice", "user-1", "chat-1");
|
|
2018
1866
|
expect(voiceRl).not.toBeNull();
|
|
2019
1867
|
expect(voiceRl!.lockedUntil).not.toBeNull();
|
|
2020
1868
|
|
|
2021
1869
|
// Telegram should be unaffected
|
|
2022
|
-
const telegramRl = getRateLimit("
|
|
1870
|
+
const telegramRl = getRateLimit("telegram", "user-1", "chat-1");
|
|
2023
1871
|
expect(telegramRl).toBeNull();
|
|
2024
1872
|
});
|
|
2025
1873
|
|
|
2026
1874
|
test("successful voice verification resets rate limit", () => {
|
|
2027
|
-
const { secret: _s } = createVerificationChallenge("
|
|
1875
|
+
const { secret: _s } = createVerificationChallenge("voice");
|
|
2028
1876
|
validateAndConsumeChallenge(
|
|
2029
|
-
"asst-1",
|
|
2030
1877
|
"voice",
|
|
2031
1878
|
"000000",
|
|
2032
1879
|
"voice-user-1",
|
|
2033
1880
|
"voice-chat-1",
|
|
2034
1881
|
);
|
|
2035
1882
|
validateAndConsumeChallenge(
|
|
2036
|
-
"asst-1",
|
|
2037
1883
|
"voice",
|
|
2038
1884
|
"111111",
|
|
2039
1885
|
"voice-user-1",
|
|
@@ -2041,9 +1887,8 @@ describe("voice guardian rate limiting", () => {
|
|
|
2041
1887
|
);
|
|
2042
1888
|
|
|
2043
1889
|
// Valid attempt should succeed (under the 5-attempt threshold)
|
|
2044
|
-
const { secret } = createVerificationChallenge("
|
|
1890
|
+
const { secret } = createVerificationChallenge("voice");
|
|
2045
1891
|
const result = validateAndConsumeChallenge(
|
|
2046
|
-
"asst-1",
|
|
2047
1892
|
"voice",
|
|
2048
1893
|
secret,
|
|
2049
1894
|
"voice-user-1",
|
|
@@ -2051,7 +1896,7 @@ describe("voice guardian rate limiting", () => {
|
|
|
2051
1896
|
);
|
|
2052
1897
|
expect(result.success).toBe(true);
|
|
2053
1898
|
|
|
2054
|
-
const rl = getRateLimit("
|
|
1899
|
+
const rl = getRateLimit("voice", "voice-user-1", "voice-chat-1");
|
|
2055
1900
|
expect(rl).not.toBeNull();
|
|
2056
1901
|
expect(rl!.invalidAttempts).toBe(0);
|
|
2057
1902
|
expect(rl!.lockedUntil).toBeNull();
|
|
@@ -2068,62 +1913,61 @@ describe("pending challenge lookup", () => {
|
|
|
2068
1913
|
});
|
|
2069
1914
|
|
|
2070
1915
|
test("findPendingChallengeForChannel returns pending challenge", () => {
|
|
2071
|
-
createVerificationChallenge("
|
|
1916
|
+
createVerificationChallenge("voice");
|
|
2072
1917
|
|
|
2073
|
-
const pending = findPendingChallengeForChannel("
|
|
1918
|
+
const pending = findPendingChallengeForChannel("voice");
|
|
2074
1919
|
expect(pending).not.toBeNull();
|
|
2075
1920
|
expect(pending!.channel).toBe("voice");
|
|
2076
1921
|
expect(pending!.status).toBe("pending");
|
|
2077
1922
|
});
|
|
2078
1923
|
|
|
2079
1924
|
test("findPendingChallengeForChannel returns null when no challenge exists", () => {
|
|
2080
|
-
const pending = findPendingChallengeForChannel("
|
|
1925
|
+
const pending = findPendingChallengeForChannel("voice");
|
|
2081
1926
|
expect(pending).toBeNull();
|
|
2082
1927
|
});
|
|
2083
1928
|
|
|
2084
1929
|
test("findPendingChallengeForChannel returns null for different channel", () => {
|
|
2085
|
-
createVerificationChallenge("
|
|
1930
|
+
createVerificationChallenge("telegram");
|
|
2086
1931
|
|
|
2087
|
-
const pending = findPendingChallengeForChannel("
|
|
1932
|
+
const pending = findPendingChallengeForChannel("voice");
|
|
2088
1933
|
expect(pending).toBeNull();
|
|
2089
1934
|
});
|
|
2090
1935
|
|
|
2091
1936
|
test("findPendingChallengeForChannel returns null after challenge is consumed", () => {
|
|
2092
|
-
const { secret } = createVerificationChallenge("
|
|
1937
|
+
const { secret } = createVerificationChallenge("voice");
|
|
2093
1938
|
validateAndConsumeChallenge(
|
|
2094
|
-
"asst-1",
|
|
2095
1939
|
"voice",
|
|
2096
1940
|
secret,
|
|
2097
1941
|
"voice-user-1",
|
|
2098
1942
|
"voice-chat-1",
|
|
2099
1943
|
);
|
|
2100
1944
|
|
|
2101
|
-
const pending = findPendingChallengeForChannel("
|
|
1945
|
+
const pending = findPendingChallengeForChannel("voice");
|
|
2102
1946
|
expect(pending).toBeNull();
|
|
2103
1947
|
});
|
|
2104
1948
|
|
|
2105
1949
|
test("getPendingChallenge service helper returns pending voice challenge", () => {
|
|
2106
|
-
createVerificationChallenge("
|
|
1950
|
+
createVerificationChallenge("voice");
|
|
2107
1951
|
|
|
2108
|
-
const pending = getPendingChallenge("
|
|
1952
|
+
const pending = getPendingChallenge("voice");
|
|
2109
1953
|
expect(pending).not.toBeNull();
|
|
2110
1954
|
expect(pending!.channel).toBe("voice");
|
|
2111
1955
|
});
|
|
2112
1956
|
|
|
2113
1957
|
test("getPendingChallenge returns null when no challenge exists", () => {
|
|
2114
|
-
const pending = getPendingChallenge("
|
|
1958
|
+
const pending = getPendingChallenge("voice");
|
|
2115
1959
|
expect(pending).toBeNull();
|
|
2116
1960
|
});
|
|
2117
1961
|
|
|
2118
1962
|
test("creating a new challenge revokes prior pending challenges", () => {
|
|
2119
|
-
createVerificationChallenge("
|
|
2120
|
-
const pending1 = findPendingChallengeForChannel("
|
|
1963
|
+
createVerificationChallenge("voice");
|
|
1964
|
+
const pending1 = findPendingChallengeForChannel("voice");
|
|
2121
1965
|
expect(pending1).not.toBeNull();
|
|
2122
1966
|
const firstId = pending1!.id;
|
|
2123
1967
|
|
|
2124
1968
|
// Creating a second challenge should revoke the first
|
|
2125
|
-
createVerificationChallenge("
|
|
2126
|
-
const pending2 = findPendingChallengeForChannel("
|
|
1969
|
+
createVerificationChallenge("voice");
|
|
1970
|
+
const pending2 = findPendingChallengeForChannel("voice");
|
|
2127
1971
|
expect(pending2).not.toBeNull();
|
|
2128
1972
|
expect(pending2!.id).not.toBe(firstId);
|
|
2129
1973
|
});
|
|
@@ -2178,7 +2022,6 @@ describe("IPC handler voice guardian verification", () => {
|
|
|
2178
2022
|
|
|
2179
2023
|
test("status for voice reflects bound state", () => {
|
|
2180
2024
|
createGuardianBinding({
|
|
2181
|
-
assistantId: "self",
|
|
2182
2025
|
channel: "voice",
|
|
2183
2026
|
guardianExternalUserId: "voice-user-1",
|
|
2184
2027
|
guardianPrincipalId: "voice-user-1",
|
|
@@ -2205,7 +2048,6 @@ describe("IPC handler voice guardian verification", () => {
|
|
|
2205
2048
|
|
|
2206
2049
|
test("revoke for voice clears active binding", () => {
|
|
2207
2050
|
createGuardianBinding({
|
|
2208
|
-
assistantId: "self",
|
|
2209
2051
|
channel: "voice",
|
|
2210
2052
|
guardianExternalUserId: "voice-user-1",
|
|
2211
2053
|
guardianPrincipalId: "voice-user-1",
|
|
@@ -2232,14 +2074,12 @@ describe("IPC handler voice guardian verification", () => {
|
|
|
2232
2074
|
|
|
2233
2075
|
test("revoke for voice does not affect telegram binding", () => {
|
|
2234
2076
|
createGuardianBinding({
|
|
2235
|
-
assistantId: "self",
|
|
2236
2077
|
channel: "voice",
|
|
2237
2078
|
guardianExternalUserId: "voice-user-1",
|
|
2238
2079
|
guardianPrincipalId: "voice-user-1",
|
|
2239
2080
|
guardianDeliveryChatId: "voice-chat-1",
|
|
2240
2081
|
});
|
|
2241
2082
|
createGuardianBinding({
|
|
2242
|
-
assistantId: "self",
|
|
2243
2083
|
channel: "telegram",
|
|
2244
2084
|
guardianExternalUserId: "tg-user-1",
|
|
2245
2085
|
guardianPrincipalId: "tg-user-1",
|
|
@@ -2273,7 +2113,6 @@ describe("outbound verification sessions", () => {
|
|
|
2273
2113
|
|
|
2274
2114
|
test("createOutboundSession creates a session with expected identity fields", () => {
|
|
2275
2115
|
const result = createOutboundSession({
|
|
2276
|
-
assistantId: "asst-1",
|
|
2277
2116
|
channel: "sms",
|
|
2278
2117
|
expectedPhoneE164: "+15551234567",
|
|
2279
2118
|
destinationAddress: "+15551234567",
|
|
@@ -2285,7 +2124,7 @@ describe("outbound verification sessions", () => {
|
|
|
2285
2124
|
expect(result.expiresAt).toBeGreaterThan(Date.now());
|
|
2286
2125
|
expect(result.ttlSeconds).toBe(600);
|
|
2287
2126
|
|
|
2288
|
-
const session = serviceFindActiveSession("
|
|
2127
|
+
const session = serviceFindActiveSession("sms");
|
|
2289
2128
|
expect(session).not.toBeNull();
|
|
2290
2129
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
2291
2130
|
expect(session!.destinationAddress).toBe("+15551234567");
|
|
@@ -2295,7 +2134,6 @@ describe("outbound verification sessions", () => {
|
|
|
2295
2134
|
|
|
2296
2135
|
test("createOutboundSession for telegram with pending_bootstrap status", () => {
|
|
2297
2136
|
const result = createOutboundSession({
|
|
2298
|
-
assistantId: "asst-1",
|
|
2299
2137
|
channel: "telegram",
|
|
2300
2138
|
identityBindingStatus: "pending_bootstrap",
|
|
2301
2139
|
destinationAddress: "@some_handle",
|
|
@@ -2303,7 +2141,7 @@ describe("outbound verification sessions", () => {
|
|
|
2303
2141
|
|
|
2304
2142
|
expect(result.sessionId).toBeDefined();
|
|
2305
2143
|
|
|
2306
|
-
const session = serviceFindActiveSession("
|
|
2144
|
+
const session = serviceFindActiveSession("telegram");
|
|
2307
2145
|
expect(session).not.toBeNull();
|
|
2308
2146
|
expect(session!.identityBindingStatus).toBe("pending_bootstrap");
|
|
2309
2147
|
expect(session!.status).toBe("pending_bootstrap");
|
|
@@ -2315,7 +2153,6 @@ describe("outbound verification sessions", () => {
|
|
|
2315
2153
|
|
|
2316
2154
|
test("validateAndConsumeChallenge succeeds with correct secret and matching identity (SMS)", () => {
|
|
2317
2155
|
const { secret } = createOutboundSession({
|
|
2318
|
-
assistantId: "asst-1",
|
|
2319
2156
|
channel: "sms",
|
|
2320
2157
|
expectedPhoneE164: "+15551234567",
|
|
2321
2158
|
expectedExternalUserId: "+15551234567",
|
|
@@ -2323,7 +2160,6 @@ describe("outbound verification sessions", () => {
|
|
|
2323
2160
|
});
|
|
2324
2161
|
|
|
2325
2162
|
const result = validateAndConsumeChallenge(
|
|
2326
|
-
"asst-1",
|
|
2327
2163
|
"sms",
|
|
2328
2164
|
secret,
|
|
2329
2165
|
"+15551234567",
|
|
@@ -2335,7 +2171,6 @@ describe("outbound verification sessions", () => {
|
|
|
2335
2171
|
|
|
2336
2172
|
test("validateAndConsumeChallenge succeeds with correct secret and matching identity (Telegram)", () => {
|
|
2337
2173
|
const { secret } = createOutboundSession({
|
|
2338
|
-
assistantId: "asst-1",
|
|
2339
2174
|
channel: "telegram",
|
|
2340
2175
|
expectedExternalUserId: "tg-user-42",
|
|
2341
2176
|
expectedChatId: "tg-chat-42",
|
|
@@ -2343,7 +2178,6 @@ describe("outbound verification sessions", () => {
|
|
|
2343
2178
|
});
|
|
2344
2179
|
|
|
2345
2180
|
const result = validateAndConsumeChallenge(
|
|
2346
|
-
"asst-1",
|
|
2347
2181
|
"telegram",
|
|
2348
2182
|
secret,
|
|
2349
2183
|
"tg-user-42",
|
|
@@ -2357,7 +2191,6 @@ describe("outbound verification sessions", () => {
|
|
|
2357
2191
|
|
|
2358
2192
|
test("validateAndConsumeChallenge rejects correct secret with wrong identity (anti-oracle)", () => {
|
|
2359
2193
|
const { secret } = createOutboundSession({
|
|
2360
|
-
assistantId: "asst-1",
|
|
2361
2194
|
channel: "sms",
|
|
2362
2195
|
expectedPhoneE164: "+15551234567",
|
|
2363
2196
|
expectedExternalUserId: "+15551234567",
|
|
@@ -2365,7 +2198,6 @@ describe("outbound verification sessions", () => {
|
|
|
2365
2198
|
});
|
|
2366
2199
|
|
|
2367
2200
|
const result = validateAndConsumeChallenge(
|
|
2368
|
-
"asst-1",
|
|
2369
2201
|
"sms",
|
|
2370
2202
|
secret,
|
|
2371
2203
|
"+15559999999",
|
|
@@ -2383,7 +2215,6 @@ describe("outbound verification sessions", () => {
|
|
|
2383
2215
|
|
|
2384
2216
|
test("validateAndConsumeChallenge rejects correct secret with wrong Telegram identity", () => {
|
|
2385
2217
|
const { secret } = createOutboundSession({
|
|
2386
|
-
assistantId: "asst-1",
|
|
2387
2218
|
channel: "telegram",
|
|
2388
2219
|
expectedExternalUserId: "tg-user-42",
|
|
2389
2220
|
expectedChatId: "tg-chat-42",
|
|
@@ -2391,7 +2222,6 @@ describe("outbound verification sessions", () => {
|
|
|
2391
2222
|
});
|
|
2392
2223
|
|
|
2393
2224
|
const result = validateAndConsumeChallenge(
|
|
2394
|
-
"asst-1",
|
|
2395
2225
|
"telegram",
|
|
2396
2226
|
secret,
|
|
2397
2227
|
"tg-user-WRONG",
|
|
@@ -2409,7 +2239,6 @@ describe("outbound verification sessions", () => {
|
|
|
2409
2239
|
const challengeHash = createHash("sha256").update(secret).digest("hex");
|
|
2410
2240
|
createVerificationSession({
|
|
2411
2241
|
id: "session-expired",
|
|
2412
|
-
assistantId: "asst-1",
|
|
2413
2242
|
channel: "sms",
|
|
2414
2243
|
challengeHash,
|
|
2415
2244
|
expiresAt: Date.now() - 1000,
|
|
@@ -2419,7 +2248,6 @@ describe("outbound verification sessions", () => {
|
|
|
2419
2248
|
});
|
|
2420
2249
|
|
|
2421
2250
|
const result = validateAndConsumeChallenge(
|
|
2422
|
-
"asst-1",
|
|
2423
2251
|
"sms",
|
|
2424
2252
|
secret,
|
|
2425
2253
|
"+15551234567",
|
|
@@ -2433,7 +2261,6 @@ describe("outbound verification sessions", () => {
|
|
|
2433
2261
|
|
|
2434
2262
|
test("revoked outbound session is rejected", () => {
|
|
2435
2263
|
const { secret, sessionId } = createOutboundSession({
|
|
2436
|
-
assistantId: "asst-1",
|
|
2437
2264
|
channel: "sms",
|
|
2438
2265
|
expectedPhoneE164: "+15551234567",
|
|
2439
2266
|
destinationAddress: "+15551234567",
|
|
@@ -2443,7 +2270,6 @@ describe("outbound verification sessions", () => {
|
|
|
2443
2270
|
serviceUpdateSessionStatus(sessionId, "revoked");
|
|
2444
2271
|
|
|
2445
2272
|
const result = validateAndConsumeChallenge(
|
|
2446
|
-
"asst-1",
|
|
2447
2273
|
"sms",
|
|
2448
2274
|
secret,
|
|
2449
2275
|
"+15551234567",
|
|
@@ -2457,7 +2283,6 @@ describe("outbound verification sessions", () => {
|
|
|
2457
2283
|
|
|
2458
2284
|
test("outbound session cannot be consumed twice (replay prevention)", () => {
|
|
2459
2285
|
const { secret } = createOutboundSession({
|
|
2460
|
-
assistantId: "asst-1",
|
|
2461
2286
|
channel: "sms",
|
|
2462
2287
|
expectedPhoneE164: "+15551234567",
|
|
2463
2288
|
expectedExternalUserId: "+15551234567",
|
|
@@ -2465,7 +2290,6 @@ describe("outbound verification sessions", () => {
|
|
|
2465
2290
|
});
|
|
2466
2291
|
|
|
2467
2292
|
const result1 = validateAndConsumeChallenge(
|
|
2468
|
-
"asst-1",
|
|
2469
2293
|
"sms",
|
|
2470
2294
|
secret,
|
|
2471
2295
|
"+15551234567",
|
|
@@ -2474,7 +2298,6 @@ describe("outbound verification sessions", () => {
|
|
|
2474
2298
|
expect(result1.success).toBe(true);
|
|
2475
2299
|
|
|
2476
2300
|
const result2 = validateAndConsumeChallenge(
|
|
2477
|
-
"asst-1",
|
|
2478
2301
|
"sms",
|
|
2479
2302
|
secret,
|
|
2480
2303
|
"+15551234567",
|
|
@@ -2486,10 +2309,9 @@ describe("outbound verification sessions", () => {
|
|
|
2486
2309
|
// ── Backward compat: existing inbound-only flow still works ──
|
|
2487
2310
|
|
|
2488
2311
|
test("backward compat: inbound-only challenge without expected identity still works", () => {
|
|
2489
|
-
const { secret } = createVerificationChallenge("
|
|
2312
|
+
const { secret } = createVerificationChallenge("telegram");
|
|
2490
2313
|
|
|
2491
2314
|
const result = validateAndConsumeChallenge(
|
|
2492
|
-
"asst-1",
|
|
2493
2315
|
"telegram",
|
|
2494
2316
|
secret,
|
|
2495
2317
|
"user-42",
|
|
@@ -2506,26 +2328,25 @@ describe("outbound verification sessions", () => {
|
|
|
2506
2328
|
|
|
2507
2329
|
test("session state transitions (pending_bootstrap → awaiting_response → verified)", () => {
|
|
2508
2330
|
const { sessionId } = createOutboundSession({
|
|
2509
|
-
assistantId: "asst-1",
|
|
2510
2331
|
channel: "telegram",
|
|
2511
2332
|
identityBindingStatus: "pending_bootstrap",
|
|
2512
2333
|
destinationAddress: "@some_handle",
|
|
2513
2334
|
});
|
|
2514
2335
|
|
|
2515
|
-
const initial = serviceFindActiveSession("
|
|
2336
|
+
const initial = serviceFindActiveSession("telegram");
|
|
2516
2337
|
expect(initial).not.toBeNull();
|
|
2517
2338
|
expect(initial!.status).toBe("pending_bootstrap");
|
|
2518
2339
|
|
|
2519
2340
|
// Transition to awaiting_response
|
|
2520
2341
|
serviceUpdateSessionStatus(sessionId, "awaiting_response");
|
|
2521
|
-
const awaiting = storeFindActiveSession("
|
|
2342
|
+
const awaiting = storeFindActiveSession("telegram");
|
|
2522
2343
|
expect(awaiting).not.toBeNull();
|
|
2523
2344
|
expect(awaiting!.status).toBe("awaiting_response");
|
|
2524
2345
|
|
|
2525
2346
|
// Transition to verified
|
|
2526
2347
|
serviceUpdateSessionStatus(sessionId, "verified");
|
|
2527
2348
|
// verified is not an "active" status, so findActiveSession returns null
|
|
2528
|
-
const active = storeFindActiveSession("
|
|
2349
|
+
const active = storeFindActiveSession("telegram");
|
|
2529
2350
|
expect(active).toBeNull();
|
|
2530
2351
|
});
|
|
2531
2352
|
|
|
@@ -2533,25 +2354,23 @@ describe("outbound verification sessions", () => {
|
|
|
2533
2354
|
|
|
2534
2355
|
test("creating a new outbound session auto-revokes prior pending/awaiting sessions", () => {
|
|
2535
2356
|
const { sessionId: firstId } = createOutboundSession({
|
|
2536
|
-
assistantId: "asst-1",
|
|
2537
2357
|
channel: "sms",
|
|
2538
2358
|
expectedPhoneE164: "+15551234567",
|
|
2539
2359
|
destinationAddress: "+15551234567",
|
|
2540
2360
|
});
|
|
2541
2361
|
|
|
2542
|
-
const first = serviceFindActiveSession("
|
|
2362
|
+
const first = serviceFindActiveSession("sms");
|
|
2543
2363
|
expect(first).not.toBeNull();
|
|
2544
2364
|
expect(first!.id).toBe(firstId);
|
|
2545
2365
|
|
|
2546
2366
|
// Create a second session — first should be revoked
|
|
2547
2367
|
const { sessionId: secondId } = createOutboundSession({
|
|
2548
|
-
assistantId: "asst-1",
|
|
2549
2368
|
channel: "sms",
|
|
2550
2369
|
expectedPhoneE164: "+15559876543",
|
|
2551
2370
|
destinationAddress: "+15559876543",
|
|
2552
2371
|
});
|
|
2553
2372
|
|
|
2554
|
-
const second = serviceFindActiveSession("
|
|
2373
|
+
const second = serviceFindActiveSession("sms");
|
|
2555
2374
|
expect(second).not.toBeNull();
|
|
2556
2375
|
expect(second!.id).toBe(secondId);
|
|
2557
2376
|
|
|
@@ -2570,20 +2389,19 @@ describe("outbound verification sessions", () => {
|
|
|
2570
2389
|
|
|
2571
2390
|
test("findActiveSession returns the most recent active session", () => {
|
|
2572
2391
|
createOutboundSession({
|
|
2573
|
-
assistantId: "asst-1",
|
|
2574
2392
|
channel: "sms",
|
|
2575
2393
|
expectedPhoneE164: "+15551234567",
|
|
2576
2394
|
destinationAddress: "+15551234567",
|
|
2577
2395
|
});
|
|
2578
2396
|
|
|
2579
|
-
const session = serviceFindActiveSession("
|
|
2397
|
+
const session = serviceFindActiveSession("sms");
|
|
2580
2398
|
expect(session).not.toBeNull();
|
|
2581
2399
|
expect(session!.status).toBe("awaiting_response");
|
|
2582
2400
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
2583
2401
|
});
|
|
2584
2402
|
|
|
2585
2403
|
test("findActiveSession returns null when no active session exists", () => {
|
|
2586
|
-
const session = serviceFindActiveSession("
|
|
2404
|
+
const session = serviceFindActiveSession("sms");
|
|
2587
2405
|
expect(session).toBeNull();
|
|
2588
2406
|
});
|
|
2589
2407
|
|
|
@@ -2591,14 +2409,12 @@ describe("outbound verification sessions", () => {
|
|
|
2591
2409
|
|
|
2592
2410
|
test("findSessionByIdentity returns session matching phone E164", () => {
|
|
2593
2411
|
createOutboundSession({
|
|
2594
|
-
assistantId: "asst-1",
|
|
2595
2412
|
channel: "sms",
|
|
2596
2413
|
expectedPhoneE164: "+15551234567",
|
|
2597
2414
|
destinationAddress: "+15551234567",
|
|
2598
2415
|
});
|
|
2599
2416
|
|
|
2600
2417
|
const session = serviceFindSessionByIdentity(
|
|
2601
|
-
"asst-1",
|
|
2602
2418
|
"sms",
|
|
2603
2419
|
undefined,
|
|
2604
2420
|
undefined,
|
|
@@ -2610,32 +2426,25 @@ describe("outbound verification sessions", () => {
|
|
|
2610
2426
|
|
|
2611
2427
|
test("findSessionByIdentity returns session matching external user ID", () => {
|
|
2612
2428
|
createOutboundSession({
|
|
2613
|
-
assistantId: "asst-1",
|
|
2614
2429
|
channel: "telegram",
|
|
2615
2430
|
expectedExternalUserId: "tg-user-42",
|
|
2616
2431
|
expectedChatId: "tg-chat-42",
|
|
2617
2432
|
destinationAddress: "tg-chat-42",
|
|
2618
2433
|
});
|
|
2619
2434
|
|
|
2620
|
-
const session = serviceFindSessionByIdentity(
|
|
2621
|
-
"asst-1",
|
|
2622
|
-
"telegram",
|
|
2623
|
-
"tg-user-42",
|
|
2624
|
-
);
|
|
2435
|
+
const session = serviceFindSessionByIdentity("telegram", "tg-user-42");
|
|
2625
2436
|
expect(session).not.toBeNull();
|
|
2626
2437
|
expect(session!.expectedExternalUserId).toBe("tg-user-42");
|
|
2627
2438
|
});
|
|
2628
2439
|
|
|
2629
2440
|
test("findSessionByIdentity returns null for non-matching identity", () => {
|
|
2630
2441
|
createOutboundSession({
|
|
2631
|
-
assistantId: "asst-1",
|
|
2632
2442
|
channel: "sms",
|
|
2633
2443
|
expectedPhoneE164: "+15551234567",
|
|
2634
2444
|
destinationAddress: "+15551234567",
|
|
2635
2445
|
});
|
|
2636
2446
|
|
|
2637
2447
|
const session = serviceFindSessionByIdentity(
|
|
2638
|
-
"asst-1",
|
|
2639
2448
|
"sms",
|
|
2640
2449
|
undefined,
|
|
2641
2450
|
undefined,
|
|
@@ -2648,13 +2457,12 @@ describe("outbound verification sessions", () => {
|
|
|
2648
2457
|
|
|
2649
2458
|
test("bindSessionIdentity transitions from pending_bootstrap to bound", () => {
|
|
2650
2459
|
const { sessionId } = createOutboundSession({
|
|
2651
|
-
assistantId: "asst-1",
|
|
2652
2460
|
channel: "telegram",
|
|
2653
2461
|
identityBindingStatus: "pending_bootstrap",
|
|
2654
2462
|
destinationAddress: "@some_handle",
|
|
2655
2463
|
});
|
|
2656
2464
|
|
|
2657
|
-
const before = serviceFindActiveSession("
|
|
2465
|
+
const before = serviceFindActiveSession("telegram");
|
|
2658
2466
|
expect(before).not.toBeNull();
|
|
2659
2467
|
expect(before!.identityBindingStatus).toBe("pending_bootstrap");
|
|
2660
2468
|
expect(before!.expectedExternalUserId).toBeNull();
|
|
@@ -2663,7 +2471,7 @@ describe("outbound verification sessions", () => {
|
|
|
2663
2471
|
// Bind the identity
|
|
2664
2472
|
serviceBindSessionIdentity(sessionId, "tg-user-42", "tg-chat-42");
|
|
2665
2473
|
|
|
2666
|
-
const after = storeFindActiveSession("
|
|
2474
|
+
const after = storeFindActiveSession("telegram");
|
|
2667
2475
|
expect(after).not.toBeNull();
|
|
2668
2476
|
expect(after!.identityBindingStatus).toBe("bound");
|
|
2669
2477
|
expect(after!.expectedExternalUserId).toBe("tg-user-42");
|
|
@@ -2674,7 +2482,6 @@ describe("outbound verification sessions", () => {
|
|
|
2674
2482
|
|
|
2675
2483
|
test("pending_bootstrap session allows consumption without identity check", () => {
|
|
2676
2484
|
const { secret } = createOutboundSession({
|
|
2677
|
-
assistantId: "asst-1",
|
|
2678
2485
|
channel: "telegram",
|
|
2679
2486
|
identityBindingStatus: "pending_bootstrap",
|
|
2680
2487
|
destinationAddress: "@some_handle",
|
|
@@ -2682,7 +2489,6 @@ describe("outbound verification sessions", () => {
|
|
|
2682
2489
|
|
|
2683
2490
|
// Any actor can consume during pending_bootstrap
|
|
2684
2491
|
const result = validateAndConsumeChallenge(
|
|
2685
|
-
"asst-1",
|
|
2686
2492
|
"telegram",
|
|
2687
2493
|
secret,
|
|
2688
2494
|
"any-user",
|
|
@@ -2696,7 +2502,6 @@ describe("outbound verification sessions", () => {
|
|
|
2696
2502
|
|
|
2697
2503
|
test("updateSessionDelivery updates delivery tracking fields", () => {
|
|
2698
2504
|
const { sessionId } = createOutboundSession({
|
|
2699
|
-
assistantId: "asst-1",
|
|
2700
2505
|
channel: "sms",
|
|
2701
2506
|
expectedPhoneE164: "+15551234567",
|
|
2702
2507
|
destinationAddress: "+15551234567",
|
|
@@ -2705,7 +2510,7 @@ describe("outbound verification sessions", () => {
|
|
|
2705
2510
|
const now = Date.now();
|
|
2706
2511
|
storeUpdateSessionDelivery(sessionId, now, 1, now + 30_000);
|
|
2707
2512
|
|
|
2708
|
-
const session = serviceFindActiveSession("
|
|
2513
|
+
const session = serviceFindActiveSession("sms");
|
|
2709
2514
|
expect(session).not.toBeNull();
|
|
2710
2515
|
expect(session!.lastSentAt).toBe(now);
|
|
2711
2516
|
expect(session!.sendCount).toBe(1);
|
|
@@ -2716,7 +2521,6 @@ describe("outbound verification sessions", () => {
|
|
|
2716
2521
|
|
|
2717
2522
|
test("Telegram identity match succeeds via chatId alone", () => {
|
|
2718
2523
|
const { secret } = createOutboundSession({
|
|
2719
|
-
assistantId: "asst-1",
|
|
2720
2524
|
channel: "telegram",
|
|
2721
2525
|
expectedChatId: "tg-chat-42",
|
|
2722
2526
|
destinationAddress: "tg-chat-42",
|
|
@@ -2724,7 +2528,6 @@ describe("outbound verification sessions", () => {
|
|
|
2724
2528
|
|
|
2725
2529
|
// Actor has a different external user ID but matching chat ID
|
|
2726
2530
|
const result = validateAndConsumeChallenge(
|
|
2727
|
-
"asst-1",
|
|
2728
2531
|
"telegram",
|
|
2729
2532
|
secret,
|
|
2730
2533
|
"tg-user-DIFFERENT",
|
|
@@ -2738,7 +2541,6 @@ describe("outbound verification sessions", () => {
|
|
|
2738
2541
|
|
|
2739
2542
|
test("SMS identity match succeeds via expectedExternalUserId", () => {
|
|
2740
2543
|
const { secret } = createOutboundSession({
|
|
2741
|
-
assistantId: "asst-1",
|
|
2742
2544
|
channel: "sms",
|
|
2743
2545
|
expectedExternalUserId: "sms-user-42",
|
|
2744
2546
|
expectedPhoneE164: "+15551234567",
|
|
@@ -2747,7 +2549,6 @@ describe("outbound verification sessions", () => {
|
|
|
2747
2549
|
|
|
2748
2550
|
// Actor matches expectedExternalUserId
|
|
2749
2551
|
const result = validateAndConsumeChallenge(
|
|
2750
|
-
"asst-1",
|
|
2751
2552
|
"sms",
|
|
2752
2553
|
secret,
|
|
2753
2554
|
"sms-user-42",
|
|
@@ -2789,7 +2590,7 @@ describe("outbound SMS verification", () => {
|
|
|
2789
2590
|
expect(resp!.channel).toBe("sms");
|
|
2790
2591
|
|
|
2791
2592
|
// Verify the session was created with expected identity
|
|
2792
|
-
const session = serviceFindActiveSession("
|
|
2593
|
+
const session = serviceFindActiveSession("sms");
|
|
2793
2594
|
expect(session).not.toBeNull();
|
|
2794
2595
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
2795
2596
|
expect(session!.destinationAddress).toBe("+15551234567");
|
|
@@ -2798,7 +2599,6 @@ describe("outbound SMS verification", () => {
|
|
|
2798
2599
|
test("start_outbound rejects when active binding exists (rebind=false)", () => {
|
|
2799
2600
|
// Create an existing guardian binding
|
|
2800
2601
|
createGuardianBinding({
|
|
2801
|
-
assistantId: "self",
|
|
2802
2602
|
channel: "sms",
|
|
2803
2603
|
guardianExternalUserId: "+15551234567",
|
|
2804
2604
|
guardianPrincipalId: "+15551234567",
|
|
@@ -2825,7 +2625,6 @@ describe("outbound SMS verification", () => {
|
|
|
2825
2625
|
test("start_outbound allows rebind when rebind=true", () => {
|
|
2826
2626
|
// Create an existing guardian binding
|
|
2827
2627
|
createGuardianBinding({
|
|
2828
|
-
assistantId: "self",
|
|
2829
2628
|
channel: "sms",
|
|
2830
2629
|
guardianExternalUserId: "+15551234567",
|
|
2831
2630
|
guardianPrincipalId: "+15551234567",
|
|
@@ -2899,7 +2698,7 @@ describe("outbound SMS verification", () => {
|
|
|
2899
2698
|
expect(startResponse!.success).toBe(true);
|
|
2900
2699
|
|
|
2901
2700
|
// Manually update the session's nextResendAt to the past to simulate cooldown elapsed
|
|
2902
|
-
const session = serviceFindActiveSession("
|
|
2701
|
+
const session = serviceFindActiveSession("sms");
|
|
2903
2702
|
expect(session).not.toBeNull();
|
|
2904
2703
|
storeUpdateSessionDelivery(
|
|
2905
2704
|
session!.id,
|
|
@@ -2942,7 +2741,7 @@ describe("outbound SMS verification", () => {
|
|
|
2942
2741
|
);
|
|
2943
2742
|
|
|
2944
2743
|
// Set the send count to MAX_SENDS_PER_SESSION and nextResendAt to the past
|
|
2945
|
-
const session = serviceFindActiveSession("
|
|
2744
|
+
const session = serviceFindActiveSession("sms");
|
|
2946
2745
|
expect(session).not.toBeNull();
|
|
2947
2746
|
storeUpdateSessionDelivery(
|
|
2948
2747
|
session!.id,
|
|
@@ -2984,7 +2783,7 @@ describe("outbound SMS verification", () => {
|
|
|
2984
2783
|
);
|
|
2985
2784
|
|
|
2986
2785
|
// Verify session exists
|
|
2987
|
-
const sessionBefore = serviceFindActiveSession("
|
|
2786
|
+
const sessionBefore = serviceFindActiveSession("sms");
|
|
2988
2787
|
expect(sessionBefore).not.toBeNull();
|
|
2989
2788
|
|
|
2990
2789
|
// Cancel it
|
|
@@ -3005,14 +2804,13 @@ describe("outbound SMS verification", () => {
|
|
|
3005
2804
|
expect(resp!.channel).toBe("sms");
|
|
3006
2805
|
|
|
3007
2806
|
// Verify session is no longer active
|
|
3008
|
-
const sessionAfter = serviceFindActiveSession("
|
|
2807
|
+
const sessionAfter = serviceFindActiveSession("sms");
|
|
3009
2808
|
expect(sessionAfter).toBeNull();
|
|
3010
2809
|
});
|
|
3011
2810
|
|
|
3012
2811
|
test("inbound SMS from expected identity + correct code succeeds", () => {
|
|
3013
2812
|
// Create an outbound session
|
|
3014
2813
|
const { secret } = createOutboundSession({
|
|
3015
|
-
assistantId: "self",
|
|
3016
2814
|
channel: "sms",
|
|
3017
2815
|
expectedPhoneE164: "+15551234567",
|
|
3018
2816
|
expectedExternalUserId: "+15551234567",
|
|
@@ -3021,7 +2819,6 @@ describe("outbound SMS verification", () => {
|
|
|
3021
2819
|
|
|
3022
2820
|
// Validate with matching identity
|
|
3023
2821
|
const result = validateAndConsumeChallenge(
|
|
3024
|
-
"self",
|
|
3025
2822
|
"sms",
|
|
3026
2823
|
secret,
|
|
3027
2824
|
"+15551234567",
|
|
@@ -3037,7 +2834,6 @@ describe("outbound SMS verification", () => {
|
|
|
3037
2834
|
test("inbound SMS from wrong identity + correct code is rejected", () => {
|
|
3038
2835
|
// Create an outbound session with expected identity +15551234567
|
|
3039
2836
|
const { secret } = createOutboundSession({
|
|
3040
|
-
assistantId: "self",
|
|
3041
2837
|
channel: "sms",
|
|
3042
2838
|
expectedPhoneE164: "+15551234567",
|
|
3043
2839
|
expectedExternalUserId: "+15551234567",
|
|
@@ -3046,7 +2842,6 @@ describe("outbound SMS verification", () => {
|
|
|
3046
2842
|
|
|
3047
2843
|
// Try to validate with a different phone number (anti-oracle: same generic error)
|
|
3048
2844
|
const result = validateAndConsumeChallenge(
|
|
3049
|
-
"self",
|
|
3050
2845
|
"sms",
|
|
3051
2846
|
secret,
|
|
3052
2847
|
"+15559999999",
|
|
@@ -3162,7 +2957,7 @@ describe("outbound SMS verification", () => {
|
|
|
3162
2957
|
expect(resp!.secret).toBeDefined();
|
|
3163
2958
|
|
|
3164
2959
|
// Verify the session was created with the normalized E.164 number
|
|
3165
|
-
const session = serviceFindActiveSession("
|
|
2960
|
+
const session = serviceFindActiveSession("sms");
|
|
3166
2961
|
expect(session).not.toBeNull();
|
|
3167
2962
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
3168
2963
|
expect(session!.destinationAddress).toBe("+15551234567");
|
|
@@ -3240,7 +3035,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3240
3035
|
expect(telegramDeliverCalls.length).toBe(0);
|
|
3241
3036
|
|
|
3242
3037
|
// Verify the session is in pending_bootstrap state
|
|
3243
|
-
const session = serviceFindActiveSession("
|
|
3038
|
+
const session = serviceFindActiveSession("telegram");
|
|
3244
3039
|
expect(session).not.toBeNull();
|
|
3245
3040
|
expect(session!.identityBindingStatus).toBe("pending_bootstrap");
|
|
3246
3041
|
// destinationAddress is normalized: '@' stripped and lowercased
|
|
@@ -3297,7 +3092,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3297
3092
|
expect(resp!.telegramBootstrapUrl).toBeUndefined();
|
|
3298
3093
|
|
|
3299
3094
|
// Verify the session was created with expected identity
|
|
3300
|
-
const session = serviceFindActiveSession("
|
|
3095
|
+
const session = serviceFindActiveSession("telegram");
|
|
3301
3096
|
expect(session).not.toBeNull();
|
|
3302
3097
|
expect(session!.expectedChatId).toBe("123456789");
|
|
3303
3098
|
expect(session!.identityBindingStatus).toBe("bound");
|
|
@@ -3333,7 +3128,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3333
3128
|
|
|
3334
3129
|
test("start_outbound for telegram rejects when active binding exists (rebind=false)", () => {
|
|
3335
3130
|
createGuardianBinding({
|
|
3336
|
-
assistantId: "self",
|
|
3337
3131
|
channel: "telegram",
|
|
3338
3132
|
guardianExternalUserId: "user-42",
|
|
3339
3133
|
guardianPrincipalId: "user-42",
|
|
@@ -3366,7 +3160,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3366
3160
|
|
|
3367
3161
|
createVerificationSession({
|
|
3368
3162
|
id: "session-bootstrap-1",
|
|
3369
|
-
assistantId: "self",
|
|
3370
3163
|
channel: "telegram",
|
|
3371
3164
|
challengeHash: "some-challenge-hash",
|
|
3372
3165
|
expiresAt: Date.now() + 600_000,
|
|
@@ -3376,7 +3169,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3376
3169
|
bootstrapTokenHash: tokenHash,
|
|
3377
3170
|
});
|
|
3378
3171
|
|
|
3379
|
-
const found = resolveBootstrapToken("
|
|
3172
|
+
const found = resolveBootstrapToken("telegram", token);
|
|
3380
3173
|
expect(found).not.toBeNull();
|
|
3381
3174
|
expect(found!.id).toBe("session-bootstrap-1");
|
|
3382
3175
|
expect(found!.status).toBe("pending_bootstrap");
|
|
@@ -3389,7 +3182,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3389
3182
|
|
|
3390
3183
|
createVerificationSession({
|
|
3391
3184
|
id: "session-bootstrap-2",
|
|
3392
|
-
assistantId: "self",
|
|
3393
3185
|
channel: "telegram",
|
|
3394
3186
|
challengeHash: "some-challenge-hash",
|
|
3395
3187
|
expiresAt: Date.now() + 600_000,
|
|
@@ -3399,7 +3191,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3399
3191
|
bootstrapTokenHash: tokenHash,
|
|
3400
3192
|
});
|
|
3401
3193
|
|
|
3402
|
-
const found = resolveBootstrapToken("
|
|
3194
|
+
const found = resolveBootstrapToken("telegram", "wrong_token");
|
|
3403
3195
|
expect(found).toBeNull();
|
|
3404
3196
|
});
|
|
3405
3197
|
|
|
@@ -3410,7 +3202,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3410
3202
|
|
|
3411
3203
|
createVerificationSession({
|
|
3412
3204
|
id: "session-bootstrap-3",
|
|
3413
|
-
assistantId: "self",
|
|
3414
3205
|
channel: "telegram",
|
|
3415
3206
|
challengeHash: "some-challenge-hash",
|
|
3416
3207
|
expiresAt: Date.now() - 1000, // already expired
|
|
@@ -3420,14 +3211,13 @@ describe("outbound Telegram verification", () => {
|
|
|
3420
3211
|
bootstrapTokenHash: tokenHash,
|
|
3421
3212
|
});
|
|
3422
3213
|
|
|
3423
|
-
const found = resolveBootstrapToken("
|
|
3214
|
+
const found = resolveBootstrapToken("telegram", token);
|
|
3424
3215
|
expect(found).toBeNull();
|
|
3425
3216
|
});
|
|
3426
3217
|
|
|
3427
3218
|
test("identity-bound consume: right chat_id + right code succeeds", () => {
|
|
3428
3219
|
// Create an awaiting_response session with expected identity
|
|
3429
3220
|
const sessionResult = createOutboundSession({
|
|
3430
|
-
assistantId: "self",
|
|
3431
3221
|
channel: "telegram",
|
|
3432
3222
|
expectedExternalUserId: "user-42",
|
|
3433
3223
|
expectedChatId: "chat-42",
|
|
@@ -3436,7 +3226,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3436
3226
|
});
|
|
3437
3227
|
|
|
3438
3228
|
const result = validateAndConsumeChallenge(
|
|
3439
|
-
"self",
|
|
3440
3229
|
"telegram",
|
|
3441
3230
|
sessionResult.secret,
|
|
3442
3231
|
"user-42",
|
|
@@ -3450,7 +3239,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3450
3239
|
|
|
3451
3240
|
test("identity mismatch: wrong chat_id + right code rejects", () => {
|
|
3452
3241
|
const sessionResult = createOutboundSession({
|
|
3453
|
-
assistantId: "self",
|
|
3454
3242
|
channel: "telegram",
|
|
3455
3243
|
expectedExternalUserId: "user-42",
|
|
3456
3244
|
expectedChatId: "chat-42",
|
|
@@ -3459,7 +3247,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3459
3247
|
});
|
|
3460
3248
|
|
|
3461
3249
|
const result = validateAndConsumeChallenge(
|
|
3462
|
-
"self",
|
|
3463
3250
|
"telegram",
|
|
3464
3251
|
sessionResult.secret,
|
|
3465
3252
|
"attacker-99",
|
|
@@ -3473,7 +3260,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3473
3260
|
|
|
3474
3261
|
test("revoked session rejects verification", () => {
|
|
3475
3262
|
const sessionResult = createOutboundSession({
|
|
3476
|
-
assistantId: "self",
|
|
3477
3263
|
channel: "telegram",
|
|
3478
3264
|
expectedExternalUserId: "user-42",
|
|
3479
3265
|
expectedChatId: "chat-42",
|
|
@@ -3485,7 +3271,6 @@ describe("outbound Telegram verification", () => {
|
|
|
3485
3271
|
serviceUpdateSessionStatus(sessionResult.sessionId, "revoked");
|
|
3486
3272
|
|
|
3487
3273
|
const result = validateAndConsumeChallenge(
|
|
3488
|
-
"self",
|
|
3489
3274
|
"telegram",
|
|
3490
3275
|
sessionResult.secret,
|
|
3491
3276
|
"user-42",
|
|
@@ -3497,10 +3282,9 @@ describe("outbound Telegram verification", () => {
|
|
|
3497
3282
|
|
|
3498
3283
|
test("inbound-only Telegram verification flow still works with bare code", () => {
|
|
3499
3284
|
// Create an inbound-only challenge (no outbound session, no expected identity)
|
|
3500
|
-
const challengeResult = createVerificationChallenge("
|
|
3285
|
+
const challengeResult = createVerificationChallenge("telegram");
|
|
3501
3286
|
|
|
3502
3287
|
const result = validateAndConsumeChallenge(
|
|
3503
|
-
"self",
|
|
3504
3288
|
"telegram",
|
|
3505
3289
|
challengeResult.secret,
|
|
3506
3290
|
"user-42",
|
|
@@ -3527,7 +3311,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3527
3311
|
);
|
|
3528
3312
|
|
|
3529
3313
|
// Fast-forward the cooldown
|
|
3530
|
-
const session = serviceFindActiveSession("
|
|
3314
|
+
const session = serviceFindActiveSession("telegram");
|
|
3531
3315
|
expect(session).not.toBeNull();
|
|
3532
3316
|
storeUpdateSessionDelivery(
|
|
3533
3317
|
session!.id,
|
|
@@ -3603,7 +3387,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3603
3387
|
startCtx,
|
|
3604
3388
|
);
|
|
3605
3389
|
|
|
3606
|
-
const session = serviceFindActiveSession("
|
|
3390
|
+
const session = serviceFindActiveSession("telegram");
|
|
3607
3391
|
expect(session).not.toBeNull();
|
|
3608
3392
|
|
|
3609
3393
|
const { ctx, lastResponse } = createMockCtx();
|
|
@@ -3622,7 +3406,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3622
3406
|
expect(resp!.success).toBe(true);
|
|
3623
3407
|
|
|
3624
3408
|
// Session should be revoked
|
|
3625
|
-
const revoked = serviceFindActiveSession("
|
|
3409
|
+
const revoked = serviceFindActiveSession("telegram");
|
|
3626
3410
|
expect(revoked).toBeNull();
|
|
3627
3411
|
});
|
|
3628
3412
|
|
|
@@ -3687,7 +3471,7 @@ describe("outbound Telegram verification", () => {
|
|
|
3687
3471
|
);
|
|
3688
3472
|
|
|
3689
3473
|
// Set the send count to MAX_SENDS_PER_SESSION and nextResendAt to the past
|
|
3690
|
-
const session = serviceFindActiveSession("
|
|
3474
|
+
const session = serviceFindActiveSession("telegram");
|
|
3691
3475
|
expect(session).not.toBeNull();
|
|
3692
3476
|
storeUpdateSessionDelivery(
|
|
3693
3477
|
session!.id,
|
|
@@ -3783,7 +3567,7 @@ describe("outbound voice verification", () => {
|
|
|
3783
3567
|
expect(resp!.channel).toBe("voice");
|
|
3784
3568
|
|
|
3785
3569
|
// Verify the session was created with expected identity
|
|
3786
|
-
const session = serviceFindActiveSession("
|
|
3570
|
+
const session = serviceFindActiveSession("voice");
|
|
3787
3571
|
expect(session).not.toBeNull();
|
|
3788
3572
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
3789
3573
|
expect(session!.destinationAddress).toBe("+15551234567");
|
|
@@ -3841,7 +3625,7 @@ describe("outbound voice verification", () => {
|
|
|
3841
3625
|
expect(resp!.secret!.length).toBe(6);
|
|
3842
3626
|
|
|
3843
3627
|
// Verify the session was created with the normalized E.164 number
|
|
3844
|
-
const session = serviceFindActiveSession("
|
|
3628
|
+
const session = serviceFindActiveSession("voice");
|
|
3845
3629
|
expect(session).not.toBeNull();
|
|
3846
3630
|
expect(session!.expectedPhoneE164).toBe("+15551234567");
|
|
3847
3631
|
expect(session!.destinationAddress).toBe("+15551234567");
|
|
@@ -3857,7 +3641,6 @@ describe("outbound voice verification", () => {
|
|
|
3857
3641
|
|
|
3858
3642
|
test("start_outbound for voice rejects when binding exists (rebind=false)", () => {
|
|
3859
3643
|
createGuardianBinding({
|
|
3860
|
-
assistantId: "self",
|
|
3861
3644
|
channel: "voice",
|
|
3862
3645
|
guardianExternalUserId: "+15551234567",
|
|
3863
3646
|
guardianPrincipalId: "+15551234567",
|
|
@@ -3946,7 +3729,7 @@ describe("outbound voice verification", () => {
|
|
|
3946
3729
|
expect(resp!.success).toBe(true);
|
|
3947
3730
|
|
|
3948
3731
|
// Session should no longer be active
|
|
3949
|
-
const session = serviceFindActiveSession("
|
|
3732
|
+
const session = serviceFindActiveSession("voice");
|
|
3950
3733
|
expect(session).toBeNull();
|
|
3951
3734
|
});
|
|
3952
3735
|
|
|
@@ -3959,7 +3742,6 @@ describe("outbound voice verification", () => {
|
|
|
3959
3742
|
db.insert(channelGuardianVerificationChallenges)
|
|
3960
3743
|
.values({
|
|
3961
3744
|
id: `rate-limit-voice-${i}`,
|
|
3962
|
-
assistantId: "self",
|
|
3963
3745
|
channel: "voice",
|
|
3964
3746
|
challengeHash: `hash-${i}`,
|
|
3965
3747
|
expiresAt: now + 600_000,
|
|
@@ -4065,7 +3847,7 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4065
3847
|
);
|
|
4066
3848
|
|
|
4067
3849
|
// Move past cooldown
|
|
4068
|
-
const session = serviceFindActiveSession("
|
|
3850
|
+
const session = serviceFindActiveSession("sms");
|
|
4069
3851
|
expect(session).not.toBeNull();
|
|
4070
3852
|
storeUpdateSessionDelivery(
|
|
4071
3853
|
session!.id,
|
|
@@ -4122,7 +3904,6 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4122
3904
|
|
|
4123
3905
|
test("bootstrap (pending_bootstrap) sessions use high-entropy hex secrets, identity-bound use 6-digit numeric", () => {
|
|
4124
3906
|
const bootstrapResult = createOutboundSession({
|
|
4125
|
-
assistantId: "asst-entropy",
|
|
4126
3907
|
channel: "telegram",
|
|
4127
3908
|
identityBindingStatus: "pending_bootstrap",
|
|
4128
3909
|
destinationAddress: "@testuser",
|
|
@@ -4135,7 +3916,6 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4135
3916
|
|
|
4136
3917
|
// Identity-bound: 6-digit numeric code
|
|
4137
3918
|
const boundResult = createOutboundSession({
|
|
4138
|
-
assistantId: "asst-entropy",
|
|
4139
3919
|
channel: "sms",
|
|
4140
3920
|
expectedPhoneE164: "+15551234567",
|
|
4141
3921
|
destinationAddress: "+15551234567",
|
|
@@ -4150,7 +3930,6 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4150
3930
|
test("all identity-bound channels (SMS, Telegram chat ID, voice) use 6-digit numeric codes", () => {
|
|
4151
3931
|
// SMS
|
|
4152
3932
|
const smsResult = createOutboundSession({
|
|
4153
|
-
assistantId: "asst-codes",
|
|
4154
3933
|
channel: "sms",
|
|
4155
3934
|
expectedPhoneE164: "+15551234567",
|
|
4156
3935
|
destinationAddress: "+15551234567",
|
|
@@ -4161,7 +3940,6 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4161
3940
|
|
|
4162
3941
|
// Telegram (bound via chat ID)
|
|
4163
3942
|
const tgResult = createOutboundSession({
|
|
4164
|
-
assistantId: "asst-codes",
|
|
4165
3943
|
channel: "telegram",
|
|
4166
3944
|
expectedChatId: "123456789",
|
|
4167
3945
|
identityBindingStatus: "bound",
|
|
@@ -4173,7 +3951,6 @@ describe("M1–M4 hardening coverage", () => {
|
|
|
4173
3951
|
|
|
4174
3952
|
// Voice
|
|
4175
3953
|
const voiceResult = createOutboundSession({
|
|
4176
|
-
assistantId: "asst-codes",
|
|
4177
3954
|
channel: "voice",
|
|
4178
3955
|
expectedPhoneE164: "+15551234567",
|
|
4179
3956
|
destinationAddress: "+15551234567",
|