@vellumai/assistant 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/ARCHITECTURE.md +4 -4
  2. package/README.md +6 -6
  3. package/bun.lock +6 -2
  4. package/docs/architecture/memory.md +4 -4
  5. package/package.json +2 -2
  6. package/src/__tests__/actor-token-service.test.ts +5 -2
  7. package/src/__tests__/assistant-feature-flags-integration.test.ts +1 -0
  8. package/src/__tests__/call-controller.test.ts +78 -0
  9. package/src/__tests__/call-domain.test.ts +148 -10
  10. package/src/__tests__/call-pointer-message-composer.test.ts +39 -49
  11. package/src/__tests__/call-pointer-messages.test.ts +105 -43
  12. package/src/__tests__/canonical-guardian-store.test.ts +44 -10
  13. package/src/__tests__/channel-approval-routes.test.ts +67 -65
  14. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -0
  15. package/src/__tests__/conversation-attention-telegram.test.ts +2 -2
  16. package/src/__tests__/deterministic-verification-control-plane.test.ts +6 -6
  17. package/src/__tests__/guardian-actions-endpoint.test.ts +7 -6
  18. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +57 -12
  19. package/src/__tests__/guardian-grant-minting.test.ts +24 -24
  20. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +205 -0
  21. package/src/__tests__/guardian-routing-invariants.test.ts +64 -25
  22. package/src/__tests__/guardian-routing-state.test.ts +4 -4
  23. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -2
  24. package/src/__tests__/inbound-invite-redemption.test.ts +8 -8
  25. package/src/__tests__/memory-retrieval.benchmark.test.ts +22 -47
  26. package/src/__tests__/no-is-trusted-guard.test.ts +77 -0
  27. package/src/__tests__/non-member-access-request.test.ts +50 -47
  28. package/src/__tests__/relay-server.test.ts +71 -0
  29. package/src/__tests__/send-endpoint-busy.test.ts +6 -0
  30. package/src/__tests__/session-tool-setup-tools-disabled.test.ts +155 -0
  31. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -0
  32. package/src/__tests__/skill-projection.benchmark.test.ts +66 -2
  33. package/src/__tests__/system-prompt.test.ts +1 -0
  34. package/src/__tests__/tool-approval-handler.test.ts +1 -1
  35. package/src/__tests__/tool-grant-request-escalation.test.ts +9 -2
  36. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +8 -1
  37. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +22 -22
  38. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -4
  39. package/src/__tests__/trusted-contact-verification.test.ts +10 -10
  40. package/src/approvals/guardian-decision-primitive.ts +29 -25
  41. package/src/approvals/guardian-request-resolvers.ts +9 -5
  42. package/src/calls/call-pointer-message-composer.ts +27 -85
  43. package/src/calls/call-pointer-messages.ts +54 -21
  44. package/src/calls/guardian-dispatch.ts +30 -0
  45. package/src/calls/relay-server.ts +13 -13
  46. package/src/config/system-prompt.ts +10 -3
  47. package/src/config/templates/BOOTSTRAP.md +6 -5
  48. package/src/config/templates/USER.md +1 -0
  49. package/src/config/user-reference.ts +44 -0
  50. package/src/daemon/handlers/guardian-actions.ts +5 -2
  51. package/src/daemon/handlers/sessions.ts +8 -3
  52. package/src/daemon/lifecycle.ts +109 -3
  53. package/src/daemon/server.ts +32 -24
  54. package/src/daemon/session-agent-loop.ts +4 -3
  55. package/src/daemon/session-lifecycle.ts +1 -9
  56. package/src/daemon/session-process.ts +2 -2
  57. package/src/daemon/session-runtime-assembly.ts +2 -0
  58. package/src/daemon/session-tool-setup.ts +10 -0
  59. package/src/daemon/session.ts +1 -0
  60. package/src/memory/canonical-guardian-store.ts +40 -0
  61. package/src/memory/conversation-crud.ts +26 -0
  62. package/src/memory/conversation-store.ts +1 -0
  63. package/src/memory/db-init.ts +8 -0
  64. package/src/memory/guardian-bindings.ts +4 -0
  65. package/src/memory/job-handlers/backfill.ts +2 -9
  66. package/src/memory/migrations/125-guardian-principal-id-columns.ts +19 -0
  67. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +210 -0
  68. package/src/memory/migrations/index.ts +2 -0
  69. package/src/memory/migrations/registry.ts +5 -0
  70. package/src/memory/schema.ts +3 -0
  71. package/src/notifications/copy-composer.ts +2 -2
  72. package/src/runtime/access-request-helper.ts +43 -28
  73. package/src/runtime/actor-trust-resolver.ts +19 -14
  74. package/src/runtime/channel-guardian-service.ts +6 -0
  75. package/src/runtime/guardian-context-resolver.ts +6 -2
  76. package/src/runtime/guardian-reply-router.ts +33 -16
  77. package/src/runtime/guardian-vellum-migration.ts +29 -5
  78. package/src/runtime/http-types.ts +0 -13
  79. package/src/runtime/local-actor-identity.ts +19 -13
  80. package/src/runtime/middleware/actor-token.ts +2 -2
  81. package/src/runtime/routes/channel-delivery-routes.ts +5 -5
  82. package/src/runtime/routes/conversation-routes.ts +45 -35
  83. package/src/runtime/routes/guardian-action-routes.ts +7 -1
  84. package/src/runtime/routes/guardian-approval-interception.ts +52 -52
  85. package/src/runtime/routes/guardian-bootstrap-routes.ts +1 -0
  86. package/src/runtime/routes/inbound-conversation.ts +7 -7
  87. package/src/runtime/routes/inbound-message-handler.ts +105 -94
  88. package/src/runtime/tool-grant-request-helper.ts +1 -0
  89. package/src/util/logger.ts +10 -0
  90. package/src/daemon/call-pointer-generators.ts +0 -59
@@ -25,8 +25,8 @@ mock.module('../util/logger.js', () => ({
25
25
  }),
26
26
  }));
27
27
 
28
- import { addPointerMessage, formatDuration, resetPointerCopyGenerator, setPointerCopyGenerator } from '../calls/call-pointer-messages.js';
29
- import { getMessages } from '../memory/conversation-store.js';
28
+ import { addPointerMessage, formatDuration, resetPointerMessageProcessor, setPointerMessageProcessor } from '../calls/call-pointer-messages.js';
29
+ import { addMessage, getMessages } from '../memory/conversation-store.js';
30
30
  import { getDb, initializeDb, resetDb } from '../memory/db.js';
31
31
  import { conversations } from '../memory/schema.js';
32
32
 
@@ -95,7 +95,7 @@ describe('addPointerMessage', () => {
95
95
  });
96
96
 
97
97
  afterEach(() => {
98
- resetPointerCopyGenerator();
98
+ resetPointerMessageProcessor();
99
99
  });
100
100
 
101
101
  afterAll(() => {
@@ -192,87 +192,149 @@ describe('addPointerMessage', () => {
192
192
  expect(text).toContain('failed: Max attempts exceeded');
193
193
  });
194
194
 
195
- // Trust-aware tests: in test env, generator is not called (NODE_ENV=test
196
- // short-circuits to fallback), so these validate the trust gating path
197
- // while still receiving deterministic text.
195
+ // Trust-aware tests
198
196
 
199
- test('untrusted audience uses deterministic fallback even with generator set', () => {
197
+ test('untrusted audience uses deterministic fallback even with processor set', () => {
200
198
  const convId = 'conv-ptr-untrusted';
201
- // standard threadType + no origin channel = untrusted
202
199
  ensureConversation(convId, { threadType: 'standard' });
203
200
 
204
- const generatorCalled = { value: false };
205
- setPointerCopyGenerator(async () => {
206
- generatorCalled.value = true;
207
- return 'generated text';
201
+ const processorCalled = { value: false };
202
+ setPointerMessageProcessor(async () => {
203
+ processorCalled.value = true;
208
204
  });
209
205
 
210
206
  addPointerMessage(convId, 'started', '+15551234567');
211
207
  const text = getLatestAssistantText(convId);
212
- // In test env, deterministic fallback is always used regardless of trust
213
208
  expect(text).toContain('Call to +15551234567 started');
209
+ // processor not called because standard threadType + no origin = untrusted
210
+ expect(processorCalled.value).toBe(false);
214
211
  });
215
212
 
216
- test('explicit untrusted audience mode skips generator', () => {
213
+ test('explicit untrusted audience mode skips processor', () => {
217
214
  const convId = 'conv-ptr-explicit-untrusted';
218
215
  ensureConversation(convId, { threadType: 'private' });
219
216
 
220
- const generatorCalled = { value: false };
221
- setPointerCopyGenerator(async () => {
222
- generatorCalled.value = true;
223
- return 'generated text';
217
+ const processorCalled = { value: false };
218
+ setPointerMessageProcessor(async () => {
219
+ processorCalled.value = true;
224
220
  });
225
221
 
226
222
  addPointerMessage(convId, 'started', '+15551234567', undefined, 'untrusted');
227
223
  const text = getLatestAssistantText(convId);
228
224
  expect(text).toContain('Call to +15551234567 started');
229
- // generator is not called because audience is explicitly untrusted
230
- expect(generatorCalled.value).toBe(false);
225
+ expect(processorCalled.value).toBe(false);
231
226
  });
232
227
 
233
- test('private threadType is detected as trusted audience', async () => {
234
- const convId = 'conv-ptr-private';
228
+ test('trusted audience routes through daemon processor with required facts', async () => {
229
+ const convId = 'conv-ptr-trusted';
235
230
  ensureConversation(convId, { threadType: 'private' });
236
231
 
237
- setPointerCopyGenerator(async () => 'generated text');
232
+ let capturedInstruction = '';
233
+ let capturedFacts: string[] = [];
234
+ setPointerMessageProcessor(async (_convId, instruction, requiredFacts) => {
235
+ capturedInstruction = instruction;
236
+ capturedFacts = requiredFacts ?? [];
237
+ });
238
238
 
239
239
  await addPointerMessage(convId, 'completed', '+15559876543', { duration: '1m' });
240
+ // Processor was called with a structured instruction
241
+ expect(capturedInstruction).toContain('[CALL_STATUS_EVENT]');
242
+ expect(capturedInstruction).toContain('+15559876543');
243
+ expect(capturedInstruction).toContain('completed');
244
+ expect(capturedInstruction).toContain('1m');
245
+ // Required facts include phone number, duration, and outcome keyword
246
+ expect(capturedFacts).toContain('+15559876543');
247
+ expect(capturedFacts).toContain('1m');
248
+ expect(capturedFacts).toContain('completed');
249
+ });
250
+
251
+ test('trusted audience falls back to deterministic on processor failure', async () => {
252
+ const convId = 'conv-ptr-processor-fail';
253
+ ensureConversation(convId, { threadType: 'private' });
254
+
255
+ setPointerMessageProcessor(async () => {
256
+ throw new Error('Daemon unavailable');
257
+ });
258
+
259
+ await addPointerMessage(convId, 'failed', '+15559876543', { reason: 'busy' });
260
+ // Falls back to deterministic — written directly to conversation store
240
261
  const text = getLatestAssistantText(convId);
241
- // In test env, falls back to deterministic even on trusted path
242
- expect(text).toContain('Call to +15559876543 completed (1m)');
262
+ expect(text).toContain('failed: busy');
243
263
  });
244
264
 
245
265
  test('vellum origin channel is detected as trusted audience', async () => {
246
266
  const convId = 'conv-ptr-vellum';
247
267
  ensureConversation(convId, { originChannel: 'vellum' });
248
268
 
249
- setPointerCopyGenerator(async () => 'generated text');
269
+ let processorCalled = false;
270
+ setPointerMessageProcessor(async () => {
271
+ processorCalled = true;
272
+ });
250
273
 
251
274
  await addPointerMessage(convId, 'failed', '+15559876543', { reason: 'busy' });
252
- const text = getLatestAssistantText(convId);
253
- expect(text).toContain('failed: busy');
275
+ expect(processorCalled).toBe(true);
254
276
  });
255
277
 
256
278
  test('missing conversation defaults to untrusted', () => {
257
- const _convId = 'conv-ptr-missing';
258
- // Don't create the conversation — trust resolution should default to untrusted
279
+ const convId = 'conv-ptr-no-signals';
280
+ ensureConversation(convId);
281
+
282
+ const processorCalled = { value: false };
283
+ setPointerMessageProcessor(async () => {
284
+ processorCalled.value = true;
285
+ });
286
+
287
+ addPointerMessage(convId, 'started', '+15551234567');
288
+ const text = getLatestAssistantText(convId);
289
+ expect(text).toContain('Call to +15551234567 started');
290
+ expect(processorCalled.value).toBe(false);
291
+ });
292
+
293
+ // Provenance trust class tests
294
+
295
+ test('guardian provenance trust class is detected as trusted audience', async () => {
296
+ const convId = 'conv-ptr-guardian-provenance';
297
+ ensureConversation(convId, { threadType: 'standard' });
298
+ // Add a user message with guardian provenance metadata
299
+ await addMessage(convId, 'user', 'hello', { provenanceTrustClass: 'guardian' });
300
+
301
+ let processorCalled = false;
302
+ setPointerMessageProcessor(async () => {
303
+ processorCalled = true;
304
+ });
305
+
306
+ await addPointerMessage(convId, 'completed', '+15559876543');
307
+ expect(processorCalled).toBe(true);
308
+ });
259
309
 
260
- const generatorCalled = { value: false };
261
- setPointerCopyGenerator(async () => {
262
- generatorCalled.value = true;
263
- return 'generated text';
310
+ test('trusted_contact provenance trust class is detected as trusted audience', async () => {
311
+ const convId = 'conv-ptr-tc-provenance';
312
+ ensureConversation(convId, { threadType: 'standard' });
313
+ // Add a user message with trusted_contact provenance metadata
314
+ await addMessage(convId, 'user', 'hello', { provenanceTrustClass: 'trusted_contact' });
315
+
316
+ let processorCalled = false;
317
+ setPointerMessageProcessor(async () => {
318
+ processorCalled = true;
264
319
  });
265
320
 
266
- // This will fail at addMessage because conversation doesn't exist,
267
- // but the trust check itself should not throw. Test just the trust
268
- // gating by using a conversation that exists but has no trust signals.
269
- const convId2 = 'conv-ptr-no-signals';
270
- ensureConversation(convId2);
321
+ await addPointerMessage(convId, 'completed', '+15559876543');
322
+ expect(processorCalled).toBe(true);
323
+ });
324
+
325
+ test('unknown provenance trust class does not grant trusted audience', () => {
326
+ const convId = 'conv-ptr-unknown-provenance';
327
+ ensureConversation(convId, { threadType: 'standard' });
328
+ addMessage(convId, 'user', 'hello', { provenanceTrustClass: 'unknown' });
271
329
 
272
- addPointerMessage(convId2, 'started', '+15551234567');
273
- const text = getLatestAssistantText(convId2);
330
+ const processorCalled = { value: false };
331
+ setPointerMessageProcessor(async () => {
332
+ processorCalled.value = true;
333
+ });
334
+
335
+ addPointerMessage(convId, 'started', '+15551234567');
336
+ const text = getLatestAssistantText(convId);
274
337
  expect(text).toContain('Call to +15551234567 started');
275
- // generator not called because standard threadType + no origin = untrusted
276
- expect(generatorCalled.value).toBe(false);
338
+ expect(processorCalled.value).toBe(false);
277
339
  });
278
340
  });
@@ -41,6 +41,10 @@ import { getDb, initializeDb, resetDb } from '../memory/db.js';
41
41
 
42
42
  initializeDb();
43
43
 
44
+ // All decisionable kinds (tool_approval, pending_question, access_request)
45
+ // require a guardianPrincipalId. Use a constant for test fixtures.
46
+ const TEST_PRINCIPAL = 'test-principal-id';
47
+
44
48
  function resetTables(): void {
45
49
  const db = getDb();
46
50
  db.run('DELETE FROM canonical_guardian_deliveries');
@@ -71,6 +75,7 @@ describe('canonical-guardian-store', () => {
71
75
  conversationId: 'conv-1',
72
76
  requesterExternalUserId: 'user-1',
73
77
  guardianExternalUserId: 'guardian-1',
78
+ guardianPrincipalId: TEST_PRINCIPAL,
74
79
  callSessionId: 'session-1',
75
80
  pendingQuestionId: 'pq-1',
76
81
  questionText: 'Can I run this tool?',
@@ -94,6 +99,7 @@ describe('canonical-guardian-store', () => {
94
99
  const req = createCanonicalGuardianRequest({
95
100
  kind: 'access_request',
96
101
  sourceType: 'channel',
102
+ guardianPrincipalId: TEST_PRINCIPAL,
97
103
  });
98
104
 
99
105
  expect(req.id).toBeTruthy();
@@ -111,6 +117,7 @@ describe('canonical-guardian-store', () => {
111
117
  const created = createCanonicalGuardianRequest({
112
118
  kind: 'tool_approval',
113
119
  sourceType: 'voice',
120
+ guardianPrincipalId: TEST_PRINCIPAL,
114
121
  });
115
122
 
116
123
  const fetched = getCanonicalGuardianRequest(created.id);
@@ -127,16 +134,16 @@ describe('canonical-guardian-store', () => {
127
134
  // ── listCanonicalGuardianRequests ─────────────────────────────────
128
135
 
129
136
  test('lists all requests with no filters', () => {
130
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice' });
131
- createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel' });
137
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice', guardianPrincipalId: TEST_PRINCIPAL });
138
+ createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel', guardianPrincipalId: TEST_PRINCIPAL });
132
139
 
133
140
  const all = listCanonicalGuardianRequests();
134
141
  expect(all).toHaveLength(2);
135
142
  });
136
143
 
137
144
  test('filters by status', () => {
138
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice' });
139
- const req2 = createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel' });
145
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice', guardianPrincipalId: TEST_PRINCIPAL });
146
+ const req2 = createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel', guardianPrincipalId: TEST_PRINCIPAL });
140
147
  updateCanonicalGuardianRequest(req2.id, { status: 'approved' });
141
148
 
142
149
  const pending = listCanonicalGuardianRequests({ status: 'pending' });
@@ -153,11 +160,13 @@ describe('canonical-guardian-store', () => {
153
160
  kind: 'tool_approval',
154
161
  sourceType: 'voice',
155
162
  guardianExternalUserId: 'guardian-A',
163
+ guardianPrincipalId: TEST_PRINCIPAL,
156
164
  });
157
165
  createCanonicalGuardianRequest({
158
166
  kind: 'tool_approval',
159
167
  sourceType: 'voice',
160
168
  guardianExternalUserId: 'guardian-B',
169
+ guardianPrincipalId: TEST_PRINCIPAL,
161
170
  });
162
171
 
163
172
  const filtered = listCanonicalGuardianRequests({ guardianExternalUserId: 'guardian-A' });
@@ -170,11 +179,13 @@ describe('canonical-guardian-store', () => {
170
179
  kind: 'tool_approval',
171
180
  sourceType: 'voice',
172
181
  conversationId: 'conv-X',
182
+ guardianPrincipalId: TEST_PRINCIPAL,
173
183
  });
174
184
  createCanonicalGuardianRequest({
175
185
  kind: 'tool_approval',
176
186
  sourceType: 'voice',
177
187
  conversationId: 'conv-Y',
188
+ guardianPrincipalId: TEST_PRINCIPAL,
178
189
  });
179
190
 
180
191
  const filtered = listCanonicalGuardianRequests({ conversationId: 'conv-X' });
@@ -182,18 +193,18 @@ describe('canonical-guardian-store', () => {
182
193
  });
183
194
 
184
195
  test('filters by sourceType', () => {
185
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice' });
186
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'channel' });
187
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'desktop' });
196
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice', guardianPrincipalId: TEST_PRINCIPAL });
197
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'channel', guardianPrincipalId: TEST_PRINCIPAL });
198
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'desktop', guardianPrincipalId: TEST_PRINCIPAL });
188
199
 
189
200
  const voiceOnly = listCanonicalGuardianRequests({ sourceType: 'voice' });
190
201
  expect(voiceOnly).toHaveLength(1);
191
202
  });
192
203
 
193
204
  test('filters by kind', () => {
194
- createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice' });
195
- createCanonicalGuardianRequest({ kind: 'pending_question', sourceType: 'voice' });
196
- createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel' });
205
+ createCanonicalGuardianRequest({ kind: 'tool_approval', sourceType: 'voice', guardianPrincipalId: TEST_PRINCIPAL });
206
+ createCanonicalGuardianRequest({ kind: 'pending_question', sourceType: 'voice', guardianPrincipalId: TEST_PRINCIPAL });
207
+ createCanonicalGuardianRequest({ kind: 'access_request', sourceType: 'channel', guardianPrincipalId: TEST_PRINCIPAL });
197
208
 
198
209
  const toolOnly = listCanonicalGuardianRequests({ kind: 'tool_approval' });
199
210
  expect(toolOnly).toHaveLength(1);
@@ -204,16 +215,19 @@ describe('canonical-guardian-store', () => {
204
215
  kind: 'tool_approval',
205
216
  sourceType: 'voice',
206
217
  guardianExternalUserId: 'guardian-A',
218
+ guardianPrincipalId: TEST_PRINCIPAL,
207
219
  });
208
220
  createCanonicalGuardianRequest({
209
221
  kind: 'tool_approval',
210
222
  sourceType: 'channel',
211
223
  guardianExternalUserId: 'guardian-A',
224
+ guardianPrincipalId: TEST_PRINCIPAL,
212
225
  });
213
226
  createCanonicalGuardianRequest({
214
227
  kind: 'access_request',
215
228
  sourceType: 'voice',
216
229
  guardianExternalUserId: 'guardian-A',
230
+ guardianPrincipalId: TEST_PRINCIPAL,
217
231
  });
218
232
 
219
233
  const filtered = listCanonicalGuardianRequests({
@@ -230,6 +244,7 @@ describe('canonical-guardian-store', () => {
230
244
  const req = createCanonicalGuardianRequest({
231
245
  kind: 'tool_approval',
232
246
  sourceType: 'voice',
247
+ guardianPrincipalId: TEST_PRINCIPAL,
233
248
  });
234
249
 
235
250
  const updated = updateCanonicalGuardianRequest(req.id, {
@@ -260,6 +275,7 @@ describe('canonical-guardian-store', () => {
260
275
  const req = createCanonicalGuardianRequest({
261
276
  kind: 'tool_approval',
262
277
  sourceType: 'voice',
278
+ guardianPrincipalId: TEST_PRINCIPAL,
263
279
  });
264
280
 
265
281
  const resolved = resolveCanonicalGuardianRequest(req.id, 'pending', {
@@ -278,6 +294,7 @@ describe('canonical-guardian-store', () => {
278
294
  const req = createCanonicalGuardianRequest({
279
295
  kind: 'tool_approval',
280
296
  sourceType: 'channel',
297
+ guardianPrincipalId: TEST_PRINCIPAL,
281
298
  });
282
299
 
283
300
  const resolved = resolveCanonicalGuardianRequest(req.id, 'pending', {
@@ -293,6 +310,7 @@ describe('canonical-guardian-store', () => {
293
310
  const req = createCanonicalGuardianRequest({
294
311
  kind: 'tool_approval',
295
312
  sourceType: 'voice',
313
+ guardianPrincipalId: TEST_PRINCIPAL,
296
314
  });
297
315
 
298
316
  // Try to resolve with wrong expected status
@@ -311,6 +329,7 @@ describe('canonical-guardian-store', () => {
311
329
  const req = createCanonicalGuardianRequest({
312
330
  kind: 'tool_approval',
313
331
  sourceType: 'voice',
332
+ guardianPrincipalId: TEST_PRINCIPAL,
314
333
  });
315
334
 
316
335
  // First resolve succeeds
@@ -353,6 +372,7 @@ describe('canonical-guardian-store', () => {
353
372
  sourceChannel: 'twilio',
354
373
  conversationId: 'conv-voice-1',
355
374
  guardianExternalUserId: 'guardian-phone',
375
+ guardianPrincipalId: TEST_PRINCIPAL,
356
376
  callSessionId: 'call-123',
357
377
  pendingQuestionId: 'pq-456',
358
378
  questionText: 'What is the gate code?',
@@ -374,6 +394,7 @@ describe('canonical-guardian-store', () => {
374
394
  conversationId: 'conv-tg-1',
375
395
  requesterExternalUserId: 'requester-tg-user',
376
396
  guardianExternalUserId: 'guardian-tg-user',
397
+ guardianPrincipalId: TEST_PRINCIPAL,
377
398
  toolName: 'execute_code',
378
399
  inputDigest: 'sha256:abcdef',
379
400
  expiresAt: new Date(Date.now() + 120_000).toISOString(),
@@ -394,6 +415,7 @@ describe('canonical-guardian-store', () => {
394
415
  sourceType: 'desktop',
395
416
  conversationId: 'conv-desktop-1',
396
417
  guardianExternalUserId: 'guardian-desktop',
418
+ guardianPrincipalId: TEST_PRINCIPAL,
397
419
  questionText: 'User wants to access settings',
398
420
  });
399
421
 
@@ -408,6 +430,7 @@ describe('canonical-guardian-store', () => {
408
430
  const req = createCanonicalGuardianRequest({
409
431
  kind: 'tool_approval',
410
432
  sourceType: 'voice',
433
+ guardianPrincipalId: TEST_PRINCIPAL,
411
434
  });
412
435
 
413
436
  const d1 = createCanonicalGuardianDelivery({
@@ -436,6 +459,7 @@ describe('canonical-guardian-store', () => {
436
459
  const req = createCanonicalGuardianRequest({
437
460
  kind: 'tool_approval',
438
461
  sourceType: 'voice',
462
+ guardianPrincipalId: TEST_PRINCIPAL,
439
463
  });
440
464
 
441
465
  const deliveries = listCanonicalGuardianDeliveries(req.id);
@@ -446,10 +470,12 @@ describe('canonical-guardian-store', () => {
446
470
  const pendingReq = createCanonicalGuardianRequest({
447
471
  kind: 'pending_question',
448
472
  sourceType: 'voice',
473
+ guardianPrincipalId: TEST_PRINCIPAL,
449
474
  });
450
475
  const resolvedReq = createCanonicalGuardianRequest({
451
476
  kind: 'pending_question',
452
477
  sourceType: 'voice',
478
+ guardianPrincipalId: TEST_PRINCIPAL,
453
479
  });
454
480
  updateCanonicalGuardianRequest(resolvedReq.id, { status: 'approved' });
455
481
 
@@ -476,6 +502,7 @@ describe('canonical-guardian-store', () => {
476
502
  const req = createCanonicalGuardianRequest({
477
503
  kind: 'pending_question',
478
504
  sourceType: 'voice',
505
+ guardianPrincipalId: TEST_PRINCIPAL,
479
506
  });
480
507
 
481
508
  createCanonicalGuardianDelivery({
@@ -498,6 +525,7 @@ describe('canonical-guardian-store', () => {
498
525
  const req = createCanonicalGuardianRequest({
499
526
  kind: 'tool_approval',
500
527
  sourceType: 'voice',
528
+ guardianPrincipalId: TEST_PRINCIPAL,
501
529
  });
502
530
  const delivery = createCanonicalGuardianDelivery({
503
531
  requestId: req.id,
@@ -525,6 +553,7 @@ describe('canonical-guardian-store', () => {
525
553
  const req = createCanonicalGuardianRequest({
526
554
  kind: 'pending_question',
527
555
  sourceType: 'voice',
556
+ guardianPrincipalId: TEST_PRINCIPAL,
528
557
  });
529
558
  createCanonicalGuardianDelivery({
530
559
  requestId: req.id,
@@ -544,10 +573,12 @@ describe('canonical-guardian-store', () => {
544
573
  const pendingReq = createCanonicalGuardianRequest({
545
574
  kind: 'pending_question',
546
575
  sourceType: 'voice',
576
+ guardianPrincipalId: TEST_PRINCIPAL,
547
577
  });
548
578
  const resolvedReq = createCanonicalGuardianRequest({
549
579
  kind: 'pending_question',
550
580
  sourceType: 'voice',
581
+ guardianPrincipalId: TEST_PRINCIPAL,
551
582
  });
552
583
  updateCanonicalGuardianRequest(resolvedReq.id, { status: 'approved' });
553
584
 
@@ -574,6 +605,7 @@ describe('canonical-guardian-store', () => {
574
605
  const req = createCanonicalGuardianRequest({
575
606
  kind: 'pending_question',
576
607
  sourceType: 'voice',
608
+ guardianPrincipalId: TEST_PRINCIPAL,
577
609
  });
578
610
 
579
611
  // Two delivery rows targeting the same chat for the same request
@@ -602,6 +634,7 @@ describe('canonical-guardian-store', () => {
602
634
  const req = createCanonicalGuardianRequest({
603
635
  kind: 'pending_question',
604
636
  sourceType: 'voice',
637
+ guardianPrincipalId: TEST_PRINCIPAL,
605
638
  });
606
639
  createCanonicalGuardianDelivery({
607
640
  requestId: req.id,
@@ -620,6 +653,7 @@ describe('canonical-guardian-store', () => {
620
653
  const req = createCanonicalGuardianRequest({
621
654
  kind: 'pending_question',
622
655
  sourceType: 'voice',
656
+ guardianPrincipalId: TEST_PRINCIPAL,
623
657
  });
624
658
  createCanonicalGuardianDelivery({
625
659
  requestId: req.id,