@vellumai/assistant 0.10.3-staging.2 → 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.
Files changed (239) hide show
  1. package/openapi.yaml +73 -56
  2. package/package.json +1 -1
  3. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  4. package/src/__tests__/assistant-stream-state.test.ts +3 -76
  5. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
  6. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  7. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  8. package/src/__tests__/channel-guardian.test.ts +82 -32
  9. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  10. package/src/__tests__/channel-reply-delivery.test.ts +6 -2
  11. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  12. package/src/__tests__/config-loader-backfill.test.ts +148 -0
  13. package/src/__tests__/consult-deadline.test.ts +60 -0
  14. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  15. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  16. package/src/__tests__/contacts-relay-reads.test.ts +6 -9
  17. package/src/__tests__/contacts-write.test.ts +0 -2
  18. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
  19. package/src/__tests__/conversation-agent-loop.test.ts +98 -7
  20. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  21. package/src/__tests__/conversation-error.test.ts +18 -0
  22. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  23. package/src/__tests__/conversation-title-service.test.ts +222 -201
  24. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  25. package/src/__tests__/delete-propagation.test.ts +5 -3
  26. package/src/__tests__/dm-backfill.test.ts +6 -4
  27. package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
  28. package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
  29. package/src/__tests__/guardian-dispatch.test.ts +50 -5
  30. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  31. package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
  32. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  33. package/src/__tests__/helpers/mock-logger.ts +1 -0
  34. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  35. package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
  36. package/src/__tests__/invite-redemption-service.test.ts +273 -53
  37. package/src/__tests__/invite-routes-http.test.ts +34 -0
  38. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  39. package/src/__tests__/list-messages-page-latest.test.ts +173 -4
  40. package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
  41. package/src/__tests__/non-member-access-request.test.ts +15 -13
  42. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  43. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  44. package/src/__tests__/persona-resolver.test.ts +75 -45
  45. package/src/__tests__/plugin-bootstrap.test.ts +13 -5
  46. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  47. package/src/__tests__/provider-usage-tracking.test.ts +1 -1
  48. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  49. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  50. package/src/__tests__/reaction-persistence.test.ts +51 -4
  51. package/src/__tests__/relay-server.test.ts +88 -31
  52. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
  53. package/src/__tests__/settings-routes.test.ts +32 -0
  54. package/src/__tests__/slack-block-formatting.test.ts +1 -38
  55. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
  56. package/src/__tests__/stt-hints.test.ts +6 -3
  57. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  58. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  59. package/src/__tests__/subagent-role-registry.test.ts +17 -4
  60. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  61. package/src/__tests__/subagent-tools.test.ts +398 -3
  62. package/src/__tests__/thread-backfill.test.ts +3 -3
  63. package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
  64. package/src/__tests__/tool-start-timestamp.test.ts +4 -3
  65. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  66. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
  67. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  68. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  69. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  70. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  71. package/src/__tests__/voice-invite-redemption.test.ts +183 -20
  72. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  73. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
  74. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  75. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  76. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  77. package/src/agent/loop-exclusive-tool.test.ts +19 -15
  78. package/src/agent/loop-native-web-search.test.ts +200 -0
  79. package/src/agent/loop.ts +108 -1
  80. package/src/api/responses/conversation-message.ts +9 -0
  81. package/src/approvals/guardian-request-resolvers.ts +16 -4
  82. package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
  83. package/src/calls/guardian-dispatch.ts +14 -11
  84. package/src/calls/inbound-trust-reader.ts +7 -1
  85. package/src/calls/relay-access-wait.ts +6 -6
  86. package/src/calls/relay-server.ts +22 -2
  87. package/src/calls/relay-setup-router.ts +10 -10
  88. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  89. package/src/cli/commands/contacts.ts +10 -7
  90. package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
  91. package/src/cli/commands/memory/worker.ts +97 -30
  92. package/src/cli/commands/plugins.ts +3 -146
  93. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
  94. package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
  95. package/src/cli/lib/publish-plugin.ts +231 -1
  96. package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
  97. package/src/config/bundled-skills/subagent/SKILL.md +16 -1
  98. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  99. package/src/config/call-site-defaults.ts +0 -6
  100. package/src/config/llm-resolver.ts +0 -3
  101. package/src/config/schemas/call-site-catalog.ts +0 -7
  102. package/src/config/schemas/heartbeat.ts +2 -5
  103. package/src/config/schemas/llm.ts +3 -12
  104. package/src/config/schemas/memory-lifecycle.ts +1 -1
  105. package/src/config/seed-inference-profiles.ts +76 -35
  106. package/src/config/sync-gated-profiles.ts +0 -3
  107. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
  108. package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
  109. package/src/contacts/contact-store.ts +27 -237
  110. package/src/contacts/contacts-write.ts +18 -58
  111. package/src/contacts/gateway-channel-read.ts +51 -0
  112. package/src/contacts/member-write-relay.ts +25 -31
  113. package/src/contacts/types.ts +3 -15
  114. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  115. package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
  116. package/src/daemon/conversation-agent-loop.ts +68 -61
  117. package/src/daemon/conversation-error.ts +7 -10
  118. package/src/daemon/conversation-tool-setup.ts +0 -10
  119. package/src/daemon/conversation.ts +10 -0
  120. package/src/daemon/external-plugins-bootstrap.ts +8 -2
  121. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  122. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  123. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
  124. package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
  125. package/src/daemon/handlers/config-channels.ts +14 -29
  126. package/src/daemon/lifecycle.ts +16 -4
  127. package/src/daemon/message-types/surfaces.ts +2 -0
  128. package/src/heartbeat/heartbeat-service.ts +5 -0
  129. package/src/home/relationship-state-writer.ts +5 -0
  130. package/src/memory/__tests__/embedding-cache.test.ts +136 -0
  131. package/src/memory/compaction-ledger-store.ts +107 -0
  132. package/src/memory/conversation-crud.ts +136 -61
  133. package/src/memory/conversation-title-service.ts +173 -24
  134. package/src/memory/embedding-backend.ts +8 -1
  135. package/src/memory/embedding-cache.ts +139 -0
  136. package/src/memory/jobs-worker.ts +75 -29
  137. package/src/memory/memory-retrospective-job.ts +5 -0
  138. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
  139. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  140. package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
  141. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
  142. package/src/memory/schema/contacts.ts +6 -2
  143. package/src/memory/schema/conversations.ts +39 -0
  144. package/src/memory/steps.ts +1090 -367
  145. package/src/memory/worker-control.ts +104 -18
  146. package/src/memory/worker-process.ts +17 -0
  147. package/src/messaging/channel-binding-metadata.ts +31 -0
  148. package/src/messaging/channel-binding-schema.ts +51 -0
  149. package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
  150. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
  151. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
  152. package/src/messaging/providers/a2a/deliver.ts +5 -1
  153. package/src/messaging/providers/a2a/transport.ts +10 -0
  154. package/src/messaging/providers/callback-routing.ts +48 -0
  155. package/src/messaging/providers/channel-transport.ts +55 -0
  156. package/src/messaging/providers/index.ts +65 -241
  157. package/src/messaging/providers/slack/binding-metadata.ts +62 -0
  158. package/src/messaging/providers/slack/transport.ts +92 -0
  159. package/src/messaging/providers/telegram-bot/transport.ts +51 -0
  160. package/src/messaging/providers/whatsapp/transport.ts +38 -0
  161. package/src/notifications/__tests__/broadcaster.test.ts +0 -8
  162. package/src/notifications/__tests__/connected-channels.test.ts +8 -36
  163. package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
  164. package/src/notifications/destination-resolver.ts +7 -23
  165. package/src/notifications/emit-signal.ts +5 -11
  166. package/src/plugins/defaults/index.ts +0 -35
  167. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
  168. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
  169. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
  170. package/src/plugins/disabled-state.ts +31 -0
  171. package/src/plugins/registry.ts +55 -12
  172. package/src/prompts/persona-resolver.ts +43 -11
  173. package/src/providers/call-site-routing.ts +41 -0
  174. package/src/providers/provider-send-message.ts +6 -0
  175. package/src/providers/ratelimit.ts +6 -0
  176. package/src/providers/registry.ts +1 -1
  177. package/src/providers/retry.ts +6 -0
  178. package/src/providers/types.ts +13 -0
  179. package/src/providers/usage-tracking.ts +6 -0
  180. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
  181. package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
  182. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  183. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
  184. package/src/runtime/access-request-helper.ts +1 -2
  185. package/src/runtime/actor-trust-resolver.ts +44 -17
  186. package/src/runtime/anchored-guardian.test.ts +7 -54
  187. package/src/runtime/anchored-guardian.ts +4 -53
  188. package/src/runtime/assistant-stream-state.ts +12 -74
  189. package/src/runtime/channel-reply-delivery.ts +3 -8
  190. package/src/runtime/guardian-vellum-migration.ts +18 -16
  191. package/src/runtime/invite-redemption-service.ts +25 -10
  192. package/src/runtime/local-actor-identity.test.ts +108 -0
  193. package/src/runtime/local-actor-identity.ts +27 -20
  194. package/src/runtime/member-verdict-cache.ts +0 -0
  195. package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
  196. package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
  197. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
  198. package/src/runtime/routes/contact-routes.ts +40 -25
  199. package/src/runtime/routes/conversation-list-routes.ts +1 -29
  200. package/src/runtime/routes/conversation-routes.ts +27 -7
  201. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
  202. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
  203. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  204. package/src/runtime/routes/settings-routes.ts +8 -3
  205. package/src/runtime/services/conversation-serializer.ts +6 -49
  206. package/src/runtime/slack-block-formatting.ts +0 -15
  207. package/src/runtime/trust-verdict-consumer.ts +36 -41
  208. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  209. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  210. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
  211. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  212. package/src/subagent/index.ts +1 -1
  213. package/src/subagent/manager.ts +245 -33
  214. package/src/subagent/types.ts +8 -1
  215. package/src/tools/registry.ts +10 -3
  216. package/src/tools/subagent/consult-deadline.ts +49 -0
  217. package/src/tools/subagent/spawn.ts +234 -5
  218. package/src/util/logger.ts +9 -0
  219. package/src/util/platform.ts +14 -0
  220. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  221. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  222. package/src/workspace/migrations/registry.ts +2 -0
  223. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  224. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  225. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  226. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
  227. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  228. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  229. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  230. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  231. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  232. package/src/plugins/defaults/advisor/config.ts +0 -21
  233. package/src/plugins/defaults/advisor/consult.ts +0 -197
  234. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  235. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  236. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  237. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  238. package/src/plugins/defaults/advisor/package.json +0 -14
  239. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Test-only member seeding.
3
+ *
4
+ * Production identity upserts no longer write ACL columns (gateway-owned).
5
+ * Tests that simulate gateway-resolved members stamp the ACL columns directly
6
+ * and warm the member-verdict cache so verdict synthesis and the sync trust
7
+ * fallback resolve the intended trust.
8
+ */
9
+
10
+ import { eq } from "drizzle-orm";
11
+
12
+ import { isChannelId } from "../../channels/types.js";
13
+ import { upsertContact } from "../../contacts/contact-store.js";
14
+ import type {
15
+ ChannelPolicy,
16
+ ChannelStatus,
17
+ ContactRole,
18
+ } from "../../contacts/types.js";
19
+ import { getDb } from "../../memory/db-connection.js";
20
+ import { contactChannels, contacts } from "../../memory/schema.js";
21
+ import { setMemberVerdict } from "../../runtime/member-verdict-cache.js";
22
+
23
+ export function seedContactChannel(params: {
24
+ sourceChannel: string;
25
+ externalUserId?: string;
26
+ externalChatId?: string;
27
+ displayName?: string;
28
+ username?: string;
29
+ contactId?: string;
30
+ role?: ContactRole;
31
+ status?: ChannelStatus;
32
+ policy?: string;
33
+ verifiedAt?: number | null;
34
+ verifiedVia?: string | null;
35
+ inviteId?: string | null;
36
+ revokedReason?: string | null;
37
+ blockedReason?: string | null;
38
+ principalId?: string | null;
39
+ }): { contactId: string; channelId: string } {
40
+ const address = params.externalUserId ?? params.externalChatId;
41
+ if (!address) throw new Error("seedContactChannel requires an address");
42
+
43
+ const contact = upsertContact({
44
+ id: params.contactId,
45
+ displayName: params.displayName ?? address,
46
+ channels: [
47
+ {
48
+ type: params.sourceChannel,
49
+ address,
50
+ externalChatId: params.externalChatId ?? null,
51
+ inviteId: params.inviteId ?? null,
52
+ },
53
+ ],
54
+ reassignConflictingChannels: !!params.contactId,
55
+ });
56
+
57
+ const db = getDb();
58
+ if (params.role !== undefined || params.principalId !== undefined) {
59
+ const set: Record<string, unknown> = {};
60
+ if (params.role !== undefined) set.role = params.role;
61
+ if (params.principalId !== undefined) set.principalId = params.principalId;
62
+ db.update(contacts).set(set).where(eq(contacts.id, contact.id)).run();
63
+ }
64
+
65
+ const channel = contact.channels.find(
66
+ (ch) => ch.type === params.sourceChannel,
67
+ )!;
68
+ const aclSet: Record<string, unknown> = { updatedAt: Date.now() };
69
+ if (params.status !== undefined) aclSet.status = params.status;
70
+ if (params.policy !== undefined) aclSet.policy = params.policy;
71
+ if (params.verifiedAt !== undefined) aclSet.verifiedAt = params.verifiedAt;
72
+ if (params.verifiedVia !== undefined) aclSet.verifiedVia = params.verifiedVia;
73
+ if (params.revokedReason !== undefined)
74
+ aclSet.revokedReason = params.revokedReason;
75
+ if (params.blockedReason !== undefined)
76
+ aclSet.blockedReason = params.blockedReason;
77
+ db.update(contactChannels)
78
+ .set(aclSet)
79
+ .where(eq(contactChannels.id, channel.id))
80
+ .run();
81
+
82
+ // Warm the verdict cache so the sync trust fallback resolves this member, as
83
+ // a gateway verdict fetch would in production.
84
+ if (isChannelId(params.sourceChannel)) {
85
+ setMemberVerdict(params.sourceChannel, address, {
86
+ trustClass: params.role === "guardian" ? "guardian" : "unknown",
87
+ canonicalSenderId: address,
88
+ contactId: contact.id,
89
+ channelId: channel.id,
90
+ status: (params.status ?? "active") as ChannelStatus,
91
+ policy: (params.policy ?? "allow") as ChannelPolicy,
92
+ });
93
+ }
94
+
95
+ return { contactId: contact.id, channelId: channel.id };
96
+ }
@@ -76,6 +76,9 @@ mock.module("../ipc/gateway-client.js", () => ({
76
76
  params?: Record<string, unknown>,
77
77
  ) => {
78
78
  gatewayIpcCalls.push({ method, params });
79
+ if (method === "contacts_get_rich") {
80
+ return richContactForId(params?.contactId as string);
81
+ }
79
82
  if (method === "record_invite_redemption") {
80
83
  return { ok: true, updated: true, mirrored: true };
81
84
  }
@@ -86,15 +89,88 @@ mock.module("../ipc/gateway-client.js", () => ({
86
89
  },
87
90
  }));
88
91
 
92
+ // Serves contacts_get_rich (the gateway ACL read backing the gate-status
93
+ // fallback) from the seeded local contact, so gate resolution sources status
94
+ // from the gateway path rather than the local channel column.
95
+ function richContactForId(contactId: string | undefined) {
96
+ if (!contactId) return undefined;
97
+ const contact = getContact(contactId);
98
+ if (!contact) return undefined;
99
+ // ACL columns live on the still-present DB rows, not the slimmed interfaces;
100
+ // read them raw to build the gateway-rich response the production read parses.
101
+ const contactRole = (
102
+ getSqlite()
103
+ .query("SELECT role FROM contacts WHERE id = ?")
104
+ .get(contact.id) as { role: string } | undefined
105
+ )?.role;
106
+ return {
107
+ ok: true,
108
+ contact: {
109
+ id: contact.id,
110
+ displayName: contact.displayName,
111
+ role: contactRole ?? "contact",
112
+ interactionCount: contact.interactionCount,
113
+ createdAt: contact.createdAt,
114
+ updatedAt: contact.updatedAt,
115
+ channels: contact.channels.map((c) => {
116
+ const acl = rawChannelAcl(c.id);
117
+ return {
118
+ id: c.id,
119
+ contactId: c.contactId,
120
+ type: c.type,
121
+ address: c.address,
122
+ isPrimary: c.isPrimary,
123
+ externalUserId: c.externalChatId,
124
+ status: acl.status,
125
+ policy: acl.policy,
126
+ verifiedAt: acl.verifiedAt,
127
+ verifiedVia: acl.verifiedVia,
128
+ lastSeenAt: acl.lastSeenAt,
129
+ interactionCount: acl.interactionCount,
130
+ lastInteraction: acl.lastInteraction,
131
+ revokedReason: acl.revokedReason,
132
+ blockedReason: acl.blockedReason,
133
+ };
134
+ }),
135
+ },
136
+ };
137
+ }
138
+
139
+ /** Read a channel's ACL columns straight off the still-present DB row. */
140
+ function rawChannelAcl(channelId: string) {
141
+ return getSqlite()
142
+ .query(
143
+ `SELECT status, policy, verified_at AS verifiedAt, verified_via AS verifiedVia,
144
+ last_seen_at AS lastSeenAt, interaction_count AS interactionCount,
145
+ last_interaction AS lastInteraction, revoked_reason AS revokedReason,
146
+ blocked_reason AS blockedReason
147
+ FROM contact_channels WHERE id = ?`,
148
+ )
149
+ .get(channelId) as {
150
+ status: string;
151
+ policy: string;
152
+ verifiedAt: number | null;
153
+ verifiedVia: string | null;
154
+ lastSeenAt: number | null;
155
+ interactionCount: number;
156
+ lastInteraction: number | null;
157
+ revokedReason: string | null;
158
+ blockedReason: string | null;
159
+ };
160
+ }
161
+
89
162
  import {
90
163
  findContactChannel,
164
+ getContact,
91
165
  upsertContact,
92
166
  } from "../contacts/contact-store.js";
93
- import { upsertContactChannel } from "../contacts/contacts-write.js";
94
- import { getDb } from "../memory/db-connection.js";
167
+ import { getDb, getSqlite } from "../memory/db-connection.js";
95
168
  import { initializeDb } from "../memory/db-init.js";
96
169
  import { createInvite, revokeInvite } from "../memory/invite-store.js";
97
- import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
170
+ import {
171
+ handleChannelInbound,
172
+ seedContactChannel,
173
+ } from "./helpers/channel-test-adapter.js";
98
174
 
99
175
  await initializeDb();
100
176
 
@@ -192,13 +268,13 @@ describe("inbound invite redemption intercept", () => {
192
268
  expect(json.memberId).toEqual(expect.any(String));
193
269
  expect(json.denied).toBeUndefined();
194
270
 
195
- // Verify the user is now an active member
271
+ // The local mirror persists the member identity row; the gateway owns the
272
+ // active ACL verdict.
196
273
  const result = findContactChannel({
197
274
  channelType: "telegram",
198
275
  address: "user-invite-123",
199
276
  });
200
277
  expect(result).not.toBeNull();
201
- expect(result!.channel.status).toBe("active");
202
278
 
203
279
  // The activation is written to the gateway via upsert_verified_channel.
204
280
  expect(
@@ -337,7 +413,7 @@ describe("inbound invite redemption intercept", () => {
337
413
 
338
414
  test("existing active member sending normal message is unaffected", async () => {
339
415
  // Pre-create an active member
340
- upsertContactChannel({
416
+ seedContactChannel({
341
417
  sourceChannel: "telegram",
342
418
  externalUserId: "user-active-member",
343
419
  externalChatId: "chat-active",
@@ -391,7 +467,7 @@ describe("inbound invite redemption intercept", () => {
391
467
  });
392
468
 
393
469
  // Pre-create an active member that will click the invite link
394
- upsertContactChannel({
470
+ seedContactChannel({
395
471
  sourceChannel: "telegram",
396
472
  externalUserId: "user-already-active",
397
473
  externalChatId: "chat-invite-test",
@@ -414,7 +490,7 @@ describe("inbound invite redemption intercept", () => {
414
490
  test("reactivation via invite preserves existing guardian-managed member display name", async () => {
415
491
  // Pre-create a revoked member named "Jeff" — the invite should preserve
416
492
  // that guardian-assigned name rather than overwriting with the Telegram name.
417
- upsertContactChannel({
493
+ seedContactChannel({
418
494
  sourceChannel: "telegram",
419
495
  externalUserId: "user-invite-123",
420
496
  externalChatId: "chat-invite-test",
@@ -423,7 +499,7 @@ describe("inbound invite redemption intercept", () => {
423
499
  displayName: "Jeff",
424
500
  });
425
501
 
426
- // Look up the contact that upsertContactChannel created so we can use
502
+ // Look up the contact that the seed created so we can use
427
503
  // its ID as the invite's contactId (satisfies the FK constraint).
428
504
  const existing = findContactChannel({
429
505
  channelType: "telegram",
@@ -453,7 +529,8 @@ describe("inbound invite redemption intercept", () => {
453
529
  externalChatId: "chat-invite-test",
454
530
  });
455
531
  expect(result).not.toBeNull();
456
- expect(result!.channel.status).toBe("active");
532
+ // The gateway owns reactivation; the local mirror preserves the
533
+ // guardian-assigned display name rather than the raw platform identity.
457
534
  expect(result!.contact.displayName).toBe("Jeff");
458
535
  });
459
536
  });