@vellumai/assistant 0.10.3 → 0.10.4-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -57,12 +57,9 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
57
57
|
}));
|
|
58
58
|
|
|
59
59
|
// ── Guardian binding mock ──
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
let
|
|
63
|
-
contact: { displayName: string };
|
|
64
|
-
channel: Record<string, unknown>;
|
|
65
|
-
} | null = null;
|
|
60
|
+
// mockGuardianDelivery controls what the gateway guardian-delivery reader
|
|
61
|
+
// returns for the source channel; non-null carries at least a displayName.
|
|
62
|
+
let mockGuardianDelivery: { displayName: string } | null = null;
|
|
66
63
|
|
|
67
64
|
mock.module("../runtime/channel-verification-service.js", () => ({
|
|
68
65
|
getGuardianBinding: () => null,
|
|
@@ -82,9 +79,17 @@ mock.module("../runtime/channel-verification-service.js", () => ({
|
|
|
82
79
|
}),
|
|
83
80
|
}));
|
|
84
81
|
|
|
85
|
-
// ──
|
|
86
|
-
mock.module("../contacts/
|
|
87
|
-
|
|
82
|
+
// ── Guardian delivery reader mock ──
|
|
83
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
84
|
+
getGuardianDelivery: async (input?: { channelTypes?: string[] }) => {
|
|
85
|
+
if (!mockGuardianDelivery) return [];
|
|
86
|
+
const channelType = input?.channelTypes?.[0] ?? "telegram";
|
|
87
|
+
return [{ channelType, status: "active", ...mockGuardianDelivery }];
|
|
88
|
+
},
|
|
89
|
+
guardianForChannel: (
|
|
90
|
+
list: Array<{ channelType: string; status: string }>,
|
|
91
|
+
channelType: string,
|
|
92
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
88
93
|
}));
|
|
89
94
|
|
|
90
95
|
// ── Pending interactions mock ──
|
|
@@ -126,7 +131,10 @@ mock.module("../prompts/user-reference.js", () => ({
|
|
|
126
131
|
|
|
127
132
|
// Import module under test AFTER mocks are set up
|
|
128
133
|
import type { ChannelId } from "../channels/types.js";
|
|
129
|
-
import {
|
|
134
|
+
import {
|
|
135
|
+
getGuardianDelivery,
|
|
136
|
+
guardianForChannel,
|
|
137
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
130
138
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
131
139
|
import { resolveGuardianName } from "../prompts/user-reference.js";
|
|
132
140
|
|
|
@@ -197,9 +205,14 @@ async function simulateNotifierPoll(params: {
|
|
|
197
205
|
|
|
198
206
|
notifiedRequestIds.set(info.requestId, conversationId);
|
|
199
207
|
|
|
200
|
-
// Resolve guardian name via the
|
|
201
|
-
const
|
|
202
|
-
|
|
208
|
+
// Resolve guardian name via the gateway guardian-delivery reader
|
|
209
|
+
const guardians = await getGuardianDelivery({
|
|
210
|
+
channelTypes: [params.sourceChannel],
|
|
211
|
+
});
|
|
212
|
+
const guardian = guardians
|
|
213
|
+
? guardianForChannel(guardians, params.sourceChannel)
|
|
214
|
+
: undefined;
|
|
215
|
+
const guardianName = resolveGuardianName(guardian?.displayName ?? undefined);
|
|
203
216
|
|
|
204
217
|
const waitingText = `Waiting for ${guardianName}'s approval...`;
|
|
205
218
|
|
|
@@ -225,7 +238,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
225
238
|
deliveredReplies.length = 0;
|
|
226
239
|
deliverShouldFail = false;
|
|
227
240
|
mockPendingApprovals = [];
|
|
228
|
-
|
|
241
|
+
mockGuardianDelivery = null;
|
|
229
242
|
});
|
|
230
243
|
|
|
231
244
|
test("sends waiting message to trusted contact when pending approval exists", async () => {
|
|
@@ -238,10 +251,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
238
251
|
},
|
|
239
252
|
];
|
|
240
253
|
|
|
241
|
-
|
|
242
|
-
contact: { displayName: "Mom" },
|
|
243
|
-
channel: {},
|
|
244
|
-
};
|
|
254
|
+
mockGuardianDelivery = { displayName: "Mom" };
|
|
245
255
|
|
|
246
256
|
const notified = new Map<string, string>();
|
|
247
257
|
const sent = await simulateNotifierPoll({
|
|
@@ -273,10 +283,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
273
283
|
},
|
|
274
284
|
];
|
|
275
285
|
|
|
276
|
-
|
|
277
|
-
contact: { displayName: "Guardian User" },
|
|
278
|
-
channel: {},
|
|
279
|
-
};
|
|
286
|
+
mockGuardianDelivery = { displayName: "Guardian User" };
|
|
280
287
|
|
|
281
288
|
const notified = new Map<string, string>();
|
|
282
289
|
await simulateNotifierPoll({
|
|
@@ -306,10 +313,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
306
313
|
];
|
|
307
314
|
|
|
308
315
|
// Guardian contact exists but has an empty displayName
|
|
309
|
-
|
|
310
|
-
contact: { displayName: "" },
|
|
311
|
-
channel: {},
|
|
312
|
-
};
|
|
316
|
+
mockGuardianDelivery = { displayName: "" };
|
|
313
317
|
|
|
314
318
|
const notified = new Map<string, string>();
|
|
315
319
|
await simulateNotifierPoll({
|
|
@@ -338,7 +342,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
338
342
|
},
|
|
339
343
|
];
|
|
340
344
|
|
|
341
|
-
|
|
345
|
+
mockGuardianDelivery = null;
|
|
342
346
|
|
|
343
347
|
const notified = new Map<string, string>();
|
|
344
348
|
await simulateNotifierPoll({
|
|
@@ -367,10 +371,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
367
371
|
},
|
|
368
372
|
];
|
|
369
373
|
|
|
370
|
-
|
|
371
|
-
contact: { displayName: "Guardian" },
|
|
372
|
-
channel: {},
|
|
373
|
-
};
|
|
374
|
+
mockGuardianDelivery = { displayName: "Guardian" };
|
|
374
375
|
|
|
375
376
|
const notified = new Map<string, string>();
|
|
376
377
|
const baseParams = {
|
|
@@ -395,10 +396,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
395
396
|
});
|
|
396
397
|
|
|
397
398
|
test("sends separate messages for different requestIds", async () => {
|
|
398
|
-
|
|
399
|
-
contact: { displayName: "Guardian" },
|
|
400
|
-
channel: {},
|
|
401
|
-
};
|
|
399
|
+
mockGuardianDelivery = { displayName: "Guardian" };
|
|
402
400
|
|
|
403
401
|
const notified = new Map<string, string>();
|
|
404
402
|
const baseParams = {
|
|
@@ -437,10 +435,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
437
435
|
});
|
|
438
436
|
|
|
439
437
|
test("concurrent pollers for different conversations do not evict each other", async () => {
|
|
440
|
-
|
|
441
|
-
contact: { displayName: "Guardian" },
|
|
442
|
-
channel: {},
|
|
443
|
-
};
|
|
438
|
+
mockGuardianDelivery = { displayName: "Guardian" };
|
|
444
439
|
|
|
445
440
|
// Shared dedupe map simulating the module-level global
|
|
446
441
|
const notified = new Map<string, string>();
|
|
@@ -589,10 +584,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
589
584
|
},
|
|
590
585
|
];
|
|
591
586
|
|
|
592
|
-
|
|
593
|
-
contact: { displayName: "Guardian" },
|
|
594
|
-
channel: {},
|
|
595
|
-
};
|
|
587
|
+
mockGuardianDelivery = { displayName: "Guardian" };
|
|
596
588
|
|
|
597
589
|
const notified = new Map<string, string>();
|
|
598
590
|
const baseParams = {
|
|
@@ -647,10 +639,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
647
639
|
},
|
|
648
640
|
];
|
|
649
641
|
|
|
650
|
-
|
|
651
|
-
contact: { displayName: "Sarah" },
|
|
652
|
-
channel: {},
|
|
653
|
-
};
|
|
642
|
+
mockGuardianDelivery = { displayName: "Sarah" };
|
|
654
643
|
|
|
655
644
|
const notified = new Map<string, string>();
|
|
656
645
|
await simulateNotifierPoll({
|
|
@@ -679,10 +668,7 @@ describe("trusted-contact pending-approval notifier", () => {
|
|
|
679
668
|
},
|
|
680
669
|
];
|
|
681
670
|
|
|
682
|
-
|
|
683
|
-
contact: { displayName: " " },
|
|
684
|
-
channel: {},
|
|
685
|
-
};
|
|
671
|
+
mockGuardianDelivery = { displayName: " " };
|
|
686
672
|
|
|
687
673
|
const notified = new Map<string, string>();
|
|
688
674
|
await simulateNotifierPoll({
|
|
@@ -157,7 +157,6 @@ mock.module("../config/env.js", () => ({
|
|
|
157
157
|
import { applyCanonicalGuardianDecision } from "../approvals/guardian-decision-primitive.js";
|
|
158
158
|
import type { ActorContext } from "../approvals/guardian-request-resolvers.js";
|
|
159
159
|
import { getResolver } from "../approvals/guardian-request-resolvers.js";
|
|
160
|
-
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
161
160
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
162
161
|
import {
|
|
163
162
|
createCanonicalGuardianRequest,
|
|
@@ -176,6 +175,7 @@ import {
|
|
|
176
175
|
waitForInlineGrant,
|
|
177
176
|
} from "../tools/tool-approval-handler.js";
|
|
178
177
|
import type { ToolContext, ToolLifecycleEvent } from "../tools/types.js";
|
|
178
|
+
import { seedContactChannel } from "./helpers/seed-contact-channel.js";
|
|
179
179
|
|
|
180
180
|
await initializeDb();
|
|
181
181
|
|
|
@@ -1352,7 +1352,7 @@ describe("(g) access_request resolver: requester code delivery", () => {
|
|
|
1352
1352
|
|
|
1353
1353
|
test("guardian-facing reply uses the requester's display name, not the raw ID", async () => {
|
|
1354
1354
|
// Seed a contact so the resolver can resolve a display name.
|
|
1355
|
-
|
|
1355
|
+
seedContactChannel({
|
|
1356
1356
|
sourceChannel: "slack",
|
|
1357
1357
|
externalUserId: REQUESTER_UID,
|
|
1358
1358
|
displayName: "Alice",
|
|
@@ -65,11 +65,13 @@ mock.module("../runtime/approval-message-composer.js", () => ({
|
|
|
65
65
|
}));
|
|
66
66
|
|
|
67
67
|
import { getResolver } from "../approvals/guardian-request-resolvers.js";
|
|
68
|
-
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
69
68
|
import { createCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
|
|
70
69
|
import { getDb } from "../memory/db-connection.js";
|
|
71
70
|
import { initializeDb } from "../memory/db-init.js";
|
|
72
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
handleChannelInbound,
|
|
73
|
+
seedContactChannel,
|
|
74
|
+
} from "./helpers/channel-test-adapter.js";
|
|
73
75
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
74
76
|
|
|
75
77
|
await initializeDb();
|
|
@@ -140,7 +142,7 @@ describe("trusted contact lifecycle notification signals", () => {
|
|
|
140
142
|
guardianPrincipalId: "guardian-user-789",
|
|
141
143
|
verifiedVia: "test",
|
|
142
144
|
});
|
|
143
|
-
|
|
145
|
+
seedContactChannel({
|
|
144
146
|
sourceChannel: "telegram",
|
|
145
147
|
externalUserId: "guardian-user-789",
|
|
146
148
|
externalChatId: "guardian-chat-789",
|
|
@@ -150,7 +152,7 @@ describe("trusted contact lifecycle notification signals", () => {
|
|
|
150
152
|
});
|
|
151
153
|
|
|
152
154
|
// Set up requester contact with a display name so payloads are enriched
|
|
153
|
-
|
|
155
|
+
seedContactChannel({
|
|
154
156
|
sourceChannel: "telegram",
|
|
155
157
|
externalUserId: "requester-user-456",
|
|
156
158
|
externalChatId: "requester-chat-456",
|
|
@@ -234,7 +236,7 @@ describe("trusted contact lifecycle notification signals", () => {
|
|
|
234
236
|
guardianPrincipalId: "guardian-user-789",
|
|
235
237
|
verifiedVia: "test",
|
|
236
238
|
});
|
|
237
|
-
|
|
239
|
+
seedContactChannel({
|
|
238
240
|
sourceChannel: "telegram",
|
|
239
241
|
externalUserId: "guardian-user-789",
|
|
240
242
|
externalChatId: "guardian-chat-789",
|
|
@@ -244,7 +246,7 @@ describe("trusted contact lifecycle notification signals", () => {
|
|
|
244
246
|
});
|
|
245
247
|
|
|
246
248
|
// Set up requester contact with a display name
|
|
247
|
-
|
|
249
|
+
seedContactChannel({
|
|
248
250
|
sourceChannel: "telegram",
|
|
249
251
|
externalUserId: "requester-user-456",
|
|
250
252
|
externalChatId: "requester-chat-456",
|
|
@@ -322,7 +324,7 @@ describe("trusted contact lifecycle notification signals", () => {
|
|
|
322
324
|
guardianPrincipalId: "guardian-user-789",
|
|
323
325
|
verifiedVia: "test",
|
|
324
326
|
});
|
|
325
|
-
|
|
327
|
+
seedContactChannel({
|
|
326
328
|
sourceChannel: "telegram",
|
|
327
329
|
externalUserId: "guardian-user-789",
|
|
328
330
|
externalChatId: "guardian-chat-789",
|
|
@@ -73,14 +73,16 @@ mock.module("../runtime/approval-message-composer.js", () => ({
|
|
|
73
73
|
}));
|
|
74
74
|
|
|
75
75
|
import { findContactChannel } from "../contacts/contact-store.js";
|
|
76
|
-
import {
|
|
77
|
-
import { getDb } from "../memory/db-connection.js";
|
|
76
|
+
import { getDb, getSqlite } from "../memory/db-connection.js";
|
|
78
77
|
import { initializeDb } from "../memory/db-init.js";
|
|
79
78
|
import {
|
|
80
79
|
createOutboundSession,
|
|
81
80
|
validateAndConsumeVerification,
|
|
82
81
|
} from "../runtime/channel-verification-service.js";
|
|
83
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
handleChannelInbound,
|
|
84
|
+
seedContactChannel,
|
|
85
|
+
} from "./helpers/channel-test-adapter.js";
|
|
84
86
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
85
87
|
|
|
86
88
|
await initializeDb();
|
|
@@ -253,7 +255,7 @@ for (const config of CHANNEL_CONFIGS) {
|
|
|
253
255
|
expect(challengeResult.verificationType).toBe("trusted_contact");
|
|
254
256
|
}
|
|
255
257
|
|
|
256
|
-
|
|
258
|
+
seedContactChannel({
|
|
257
259
|
sourceChannel: config.channel,
|
|
258
260
|
externalUserId: config.senderExternalUserId,
|
|
259
261
|
externalChatId: config.externalChatId,
|
|
@@ -269,14 +271,21 @@ for (const config of CHANNEL_CONFIGS) {
|
|
|
269
271
|
});
|
|
270
272
|
|
|
271
273
|
expect(contactResult).not.toBeNull();
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
// Assert the gateway dual-write landed in the local ACL columns.
|
|
275
|
+
const acl = getSqlite()
|
|
276
|
+
.query("SELECT status, policy FROM contact_channels WHERE id = ?")
|
|
277
|
+
.get(contactResult!.channel.id) as {
|
|
278
|
+
status: string;
|
|
279
|
+
policy: string;
|
|
280
|
+
} | null;
|
|
281
|
+
expect(acl!.status).toBe("active");
|
|
282
|
+
expect(acl!.policy).toBe("allow");
|
|
274
283
|
expect(contactResult!.channel.type).toBe(config.channel);
|
|
275
284
|
});
|
|
276
285
|
|
|
277
286
|
test("no cross-channel leakage between member records", () => {
|
|
278
287
|
// Create a member for this channel
|
|
279
|
-
|
|
288
|
+
seedContactChannel({
|
|
280
289
|
sourceChannel: config.channel,
|
|
281
290
|
externalUserId: config.senderExternalUserId,
|
|
282
291
|
externalChatId: config.externalChatId,
|
|
@@ -22,21 +22,27 @@ mock.module("../util/logger.js", () => ({
|
|
|
22
22
|
}),
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from "../contacts/contact-store.js";
|
|
25
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
26
|
+
|
|
27
|
+
import type { ChannelId } from "../channels/types.js";
|
|
28
|
+
import { findContactChannel } from "../contacts/contact-store.js";
|
|
29
29
|
import {
|
|
30
30
|
revokeMember,
|
|
31
31
|
upsertContactChannel,
|
|
32
32
|
} from "../contacts/contacts-write.js";
|
|
33
|
+
import type { ChannelStatus } from "../contacts/types.js";
|
|
33
34
|
import { getDb } from "../memory/db-connection.js";
|
|
34
35
|
import { initializeDb } from "../memory/db-init.js";
|
|
36
|
+
import { contactChannels, contacts } from "../memory/schema.js";
|
|
35
37
|
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
36
38
|
import {
|
|
37
39
|
createOutboundSession,
|
|
38
40
|
validateAndConsumeVerification,
|
|
39
41
|
} from "../runtime/channel-verification-service.js";
|
|
42
|
+
import {
|
|
43
|
+
__resetMemberVerdictCacheForTest,
|
|
44
|
+
setMemberVerdict,
|
|
45
|
+
} from "../runtime/member-verdict-cache.js";
|
|
40
46
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
41
47
|
|
|
42
48
|
await initializeDb();
|
|
@@ -53,6 +59,48 @@ function resetTables(): void {
|
|
|
53
59
|
db.run("DELETE FROM contacts");
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
// Mirror a warmed gateway verdict so the sync resolveActorTrust fallback
|
|
63
|
+
// resolves the member with the given status; the local ACL columns are no
|
|
64
|
+
// longer read.
|
|
65
|
+
function warmMemberVerdict(
|
|
66
|
+
channelType: ChannelId,
|
|
67
|
+
address: string,
|
|
68
|
+
status: ChannelStatus = "unverified",
|
|
69
|
+
): void {
|
|
70
|
+
const found = findContactChannel({ channelType, address });
|
|
71
|
+
if (!found) return;
|
|
72
|
+
setMemberVerdict(channelType, address, {
|
|
73
|
+
trustClass: "unknown",
|
|
74
|
+
canonicalSenderId: address,
|
|
75
|
+
contactId: found.contact.id,
|
|
76
|
+
channelId: found.channel.id,
|
|
77
|
+
status,
|
|
78
|
+
policy: "allow",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read the local active guardian channel for a channel type, mirroring the
|
|
84
|
+
* gateway's role/status resolution. Used by assertions that confirm the local
|
|
85
|
+
* guardian binding state after verification flows.
|
|
86
|
+
*/
|
|
87
|
+
function localGuardianForChannel(channelType: string) {
|
|
88
|
+
const row = getDb()
|
|
89
|
+
.select({ contact: contacts, channel: contactChannels })
|
|
90
|
+
.from(contacts)
|
|
91
|
+
.innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
|
|
92
|
+
.where(
|
|
93
|
+
and(
|
|
94
|
+
eq(contacts.role, "guardian"),
|
|
95
|
+
eq(contactChannels.type, channelType),
|
|
96
|
+
eq(contactChannels.status, "active"),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
.orderBy(desc(contactChannels.verifiedAt))
|
|
100
|
+
.get();
|
|
101
|
+
return row ? { contact: row.contact, channel: row.channel } : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
56
104
|
// ---------------------------------------------------------------------------
|
|
57
105
|
// Tests
|
|
58
106
|
// ---------------------------------------------------------------------------
|
|
@@ -60,6 +108,7 @@ function resetTables(): void {
|
|
|
60
108
|
describe("trusted contact verification → member activation", () => {
|
|
61
109
|
beforeEach(() => {
|
|
62
110
|
resetTables();
|
|
111
|
+
__resetMemberVerdictCacheForTest();
|
|
63
112
|
});
|
|
64
113
|
|
|
65
114
|
test("successful verification creates active member with allow policy", () => {
|
|
@@ -88,26 +137,24 @@ describe("trusted contact verification → member activation", () => {
|
|
|
88
137
|
expect(result.verificationType).toBe("trusted_contact");
|
|
89
138
|
}
|
|
90
139
|
|
|
91
|
-
// Simulate the member upsert that inbound-message-handler performs on
|
|
140
|
+
// Simulate the member upsert that inbound-message-handler performs on
|
|
141
|
+
// success. The local mirror persists identity only; the gateway owns the
|
|
142
|
+
// ACL verdict, so the channel lands at the schema-default status.
|
|
92
143
|
upsertContactChannel({
|
|
93
144
|
sourceChannel: "telegram",
|
|
94
145
|
externalUserId: "requester-user-123",
|
|
95
146
|
externalChatId: "requester-chat-123",
|
|
96
|
-
status: "active",
|
|
97
|
-
policy: "allow",
|
|
98
147
|
displayName: "Requester Name",
|
|
99
148
|
username: "requester_username",
|
|
100
149
|
});
|
|
101
150
|
|
|
102
|
-
// Verify:
|
|
151
|
+
// Verify: member identity record exists
|
|
103
152
|
const contactResult = findContactChannel({
|
|
104
153
|
channelType: "telegram",
|
|
105
154
|
address: "requester-user-123",
|
|
106
155
|
});
|
|
107
156
|
|
|
108
157
|
expect(contactResult).not.toBeNull();
|
|
109
|
-
expect(contactResult!.channel.status).toBe("active");
|
|
110
|
-
expect(contactResult!.channel.policy).toBe("allow");
|
|
111
158
|
expect(contactResult!.channel.address).toBe("requester-user-123");
|
|
112
159
|
expect(contactResult!.channel.externalChatId).toBe("requester-chat-123");
|
|
113
160
|
expect(contactResult!.contact.displayName).toBe("Requester Name");
|
|
@@ -119,11 +166,10 @@ describe("trusted contact verification → member activation", () => {
|
|
|
119
166
|
sourceChannel: "telegram",
|
|
120
167
|
externalUserId: "requester-user-jeff",
|
|
121
168
|
externalChatId: "requester-chat-jeff",
|
|
122
|
-
status: "active",
|
|
123
|
-
policy: "allow",
|
|
124
169
|
displayName: "Jeff",
|
|
125
170
|
username: "jeff_handle",
|
|
126
171
|
});
|
|
172
|
+
warmMemberVerdict("telegram", "requester-user-jeff");
|
|
127
173
|
|
|
128
174
|
const trust = resolveActorTrust({
|
|
129
175
|
assistantId: "self",
|
|
@@ -132,7 +178,9 @@ describe("trusted contact verification → member activation", () => {
|
|
|
132
178
|
actorExternalId: "requester-user-jeff",
|
|
133
179
|
});
|
|
134
180
|
|
|
135
|
-
|
|
181
|
+
// The local mirror persists identity only; the schema-default status places
|
|
182
|
+
// the contact in the unverified_contact tier (gateway owns elevation).
|
|
183
|
+
expect(trust.trustClass).toBe("unverified_contact");
|
|
136
184
|
expect(trust.actorMetadata.displayName).toBe("Jeff");
|
|
137
185
|
expect(trust.actorMetadata.senderDisplayName).toBeUndefined();
|
|
138
186
|
expect(trust.actorMetadata.memberDisplayName).toBe("Jeff");
|
|
@@ -147,11 +195,10 @@ describe("trusted contact verification → member activation", () => {
|
|
|
147
195
|
sourceChannel: "telegram",
|
|
148
196
|
externalUserId: "requester-user-jeff-priority",
|
|
149
197
|
externalChatId: "requester-chat-jeff-priority",
|
|
150
|
-
status: "active",
|
|
151
|
-
policy: "allow",
|
|
152
198
|
displayName: "Jeff",
|
|
153
199
|
username: "jeff_handle",
|
|
154
200
|
});
|
|
201
|
+
warmMemberVerdict("telegram", "requester-user-jeff-priority");
|
|
155
202
|
|
|
156
203
|
const trust = resolveActorTrust({
|
|
157
204
|
assistantId: "self",
|
|
@@ -162,7 +209,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
162
209
|
actorDisplayName: "Jeffrey",
|
|
163
210
|
});
|
|
164
211
|
|
|
165
|
-
expect(trust.trustClass).toBe("
|
|
212
|
+
expect(trust.trustClass).toBe("unverified_contact");
|
|
166
213
|
expect(trust.actorMetadata.displayName).toBe("Jeff");
|
|
167
214
|
expect(trust.actorMetadata.senderDisplayName).toBe("Jeffrey");
|
|
168
215
|
expect(trust.actorMetadata.memberDisplayName).toBe("Jeff");
|
|
@@ -179,8 +226,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
179
226
|
sourceChannel: "telegram",
|
|
180
227
|
externalUserId: "other-user-in-group",
|
|
181
228
|
externalChatId: "shared-group-chat",
|
|
182
|
-
status: "active",
|
|
183
|
-
policy: "allow",
|
|
184
229
|
displayName: "Other User",
|
|
185
230
|
username: "other_handle",
|
|
186
231
|
});
|
|
@@ -229,20 +274,17 @@ describe("trusted contact verification → member activation", () => {
|
|
|
229
274
|
sourceChannel: "telegram",
|
|
230
275
|
externalUserId: "requester-user-456",
|
|
231
276
|
externalChatId: "requester-chat-456",
|
|
232
|
-
status: "active",
|
|
233
|
-
policy: "allow",
|
|
234
277
|
});
|
|
235
278
|
|
|
236
|
-
//
|
|
279
|
+
// The local mirror persists the member identity; the gateway owns the ACL
|
|
280
|
+
// verdict the inbound handler enforces.
|
|
237
281
|
const contactResult = findContactChannel({
|
|
238
282
|
channelType: "telegram",
|
|
239
283
|
address: "requester-user-456",
|
|
240
284
|
});
|
|
241
285
|
|
|
242
286
|
expect(contactResult).not.toBeNull();
|
|
243
|
-
expect(contactResult!.channel.
|
|
244
|
-
expect(contactResult!.channel.policy).toBe("allow");
|
|
245
|
-
// ACL check passes: member exists, is active, and has allow policy
|
|
287
|
+
expect(contactResult!.channel.address).toBe("requester-user-456");
|
|
246
288
|
});
|
|
247
289
|
|
|
248
290
|
test("member lookup is scoped by channel type", () => {
|
|
@@ -267,8 +309,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
267
309
|
sourceChannel: "telegram",
|
|
268
310
|
externalUserId: "user-cross-test",
|
|
269
311
|
externalChatId: "chat-cross-test",
|
|
270
|
-
status: "active",
|
|
271
|
-
policy: "allow",
|
|
272
312
|
});
|
|
273
313
|
|
|
274
314
|
// Member should be found via contacts
|
|
@@ -277,7 +317,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
277
317
|
address: "user-cross-test",
|
|
278
318
|
});
|
|
279
319
|
expect(contactResult).not.toBeNull();
|
|
280
|
-
expect(contactResult!.channel.
|
|
320
|
+
expect(contactResult!.channel.address).toBe("user-cross-test");
|
|
281
321
|
|
|
282
322
|
// Member should NOT be found for a different channel type
|
|
283
323
|
const otherChannel = findContactChannel({
|
|
@@ -287,31 +327,22 @@ describe("trusted contact verification → member activation", () => {
|
|
|
287
327
|
expect(otherChannel).toBeNull();
|
|
288
328
|
});
|
|
289
329
|
|
|
290
|
-
test("
|
|
291
|
-
// Create
|
|
330
|
+
test("revokeMember resolves the member identity without mutating local ACL", () => {
|
|
331
|
+
// Create a member identity row.
|
|
292
332
|
const member = upsertContactChannel({
|
|
293
333
|
sourceChannel: "telegram",
|
|
294
334
|
externalUserId: "user-revoked",
|
|
295
335
|
externalChatId: "chat-revoked",
|
|
296
|
-
status: "active",
|
|
297
|
-
policy: "allow",
|
|
298
336
|
displayName: "Revoked User",
|
|
299
337
|
});
|
|
300
338
|
|
|
301
|
-
//
|
|
302
|
-
|
|
339
|
+
// The local revoke is a pure resolver now — the gateway owns the ACL
|
|
340
|
+
// downgrade; revokeMember returns the resolved native contact/channel.
|
|
341
|
+
const revoked = revokeMember(member!.channel.id);
|
|
303
342
|
expect(revoked).not.toBeNull();
|
|
304
|
-
expect(revoked!.channel.
|
|
305
|
-
|
|
306
|
-
// Verify the member is indeed revoked (ACL would reject)
|
|
307
|
-
const revokedResult = findContactChannel({
|
|
308
|
-
channelType: "telegram",
|
|
309
|
-
address: "user-revoked",
|
|
310
|
-
});
|
|
311
|
-
expect(revokedResult).not.toBeNull();
|
|
312
|
-
expect(revokedResult!.channel.status).toBe("revoked");
|
|
343
|
+
expect(revoked!.channel.id).toBe(member!.channel.id);
|
|
313
344
|
|
|
314
|
-
//
|
|
345
|
+
// Re-upsert on the same identity is idempotent on the identity row.
|
|
315
346
|
const session = createOutboundSession({
|
|
316
347
|
channel: "telegram",
|
|
317
348
|
expectedExternalUserId: "user-revoked",
|
|
@@ -321,7 +352,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
321
352
|
verificationPurpose: "trusted_contact",
|
|
322
353
|
});
|
|
323
354
|
|
|
324
|
-
// Requester enters the new code
|
|
325
355
|
const result = validateAndConsumeVerification(
|
|
326
356
|
"telegram",
|
|
327
357
|
session.secret,
|
|
@@ -333,23 +363,18 @@ describe("trusted contact verification → member activation", () => {
|
|
|
333
363
|
expect(result.verificationType).toBe("trusted_contact");
|
|
334
364
|
}
|
|
335
365
|
|
|
336
|
-
// upsertContactChannel reactivates the existing record
|
|
337
366
|
upsertContactChannel({
|
|
338
367
|
sourceChannel: "telegram",
|
|
339
368
|
externalUserId: "user-revoked",
|
|
340
369
|
externalChatId: "chat-revoked",
|
|
341
|
-
status: "active",
|
|
342
|
-
policy: "allow",
|
|
343
370
|
});
|
|
344
371
|
|
|
345
|
-
|
|
346
|
-
const reactivatedResult = findContactChannel({
|
|
372
|
+
const reResolved = findContactChannel({
|
|
347
373
|
channelType: "telegram",
|
|
348
374
|
address: "user-revoked",
|
|
349
375
|
});
|
|
350
|
-
expect(
|
|
351
|
-
expect(
|
|
352
|
-
expect(reactivatedResult!.channel.policy).toBe("allow");
|
|
376
|
+
expect(reResolved).not.toBeNull();
|
|
377
|
+
expect(reResolved!.channel.id).toBe(member!.channel.id);
|
|
353
378
|
});
|
|
354
379
|
|
|
355
380
|
test("trusted contact verification does NOT create a guardian binding", () => {
|
|
@@ -388,7 +413,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
388
413
|
}
|
|
389
414
|
|
|
390
415
|
// The original guardian binding should remain intact
|
|
391
|
-
const guardianResult =
|
|
416
|
+
const guardianResult = localGuardianForChannel("telegram");
|
|
392
417
|
expect(guardianResult).not.toBeNull();
|
|
393
418
|
expect(guardianResult!.channel.address).toBe("guardian-user-original");
|
|
394
419
|
});
|
|
@@ -412,7 +437,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
412
437
|
expect(result.verificationType).toBe("guardian");
|
|
413
438
|
}
|
|
414
439
|
|
|
415
|
-
const guardianResult =
|
|
440
|
+
const guardianResult = localGuardianForChannel("telegram");
|
|
416
441
|
expect(guardianResult).toBeNull();
|
|
417
442
|
});
|
|
418
443
|
});
|