@vellumai/assistant 0.10.2-dev.202606251104.36cd100 → 0.10.2-dev.202606251348.a66ca6e
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 +0 -35
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +36 -27
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- 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-attention-telegram.test.ts +9 -11
- 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/seed-contact-channel.ts +77 -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__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +5 -5
- package/src/__tests__/plugin-route-contribution.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +2 -2
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -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__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +0 -2
- package/src/__tests__/thread-backfill.test.ts +3 -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 +13 -7
- package/src/__tests__/trusted-contact-verification.test.ts +50 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +138 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +27 -47
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +7 -15
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/relay-access-wait.ts +9 -7
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/plugins.ts +194 -0
- package/src/cli/lib/__tests__/publish-plugin.test.ts +306 -0
- package/src/cli/lib/publish-plugin.ts +403 -0
- 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 +43 -227
- 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 +2 -15
- package/src/daemon/external-plugins-bootstrap.ts +5 -5
- 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 +11 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/hooks/hook-loader.ts +13 -13
- package/src/memory/memory-retrospective-job.ts +5 -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/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +9 -9
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +2 -2
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/mtime-cache.ts +5 -8
- package/src/plugins/pipeline.ts +2 -2
- package/src/plugins/registry.ts +5 -5
- package/src/plugins/types.ts +7 -7
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +17 -18
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +114 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -15
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- 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/routes/__tests__/contact-routes.test.ts +95 -2
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +18 -11
- 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 +10 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/trust-verdict-consumer.ts +28 -41
- package/src/tools/types.ts +114 -23
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
package/openapi.yaml
CHANGED
|
@@ -4915,20 +4915,13 @@ paths:
|
|
|
4915
4915
|
- address
|
|
4916
4916
|
- isPrimary
|
|
4917
4917
|
- externalUserId
|
|
4918
|
-
- status
|
|
4919
|
-
- policy
|
|
4920
|
-
- verifiedAt
|
|
4921
|
-
- verifiedVia
|
|
4922
4918
|
- lastSeenAt
|
|
4923
4919
|
- interactionCount
|
|
4924
4920
|
- lastInteraction
|
|
4925
|
-
- revokedReason
|
|
4926
|
-
- blockedReason
|
|
4927
4921
|
additionalProperties: false
|
|
4928
4922
|
required:
|
|
4929
4923
|
- id
|
|
4930
4924
|
- displayName
|
|
4931
|
-
- role
|
|
4932
4925
|
- interactionCount
|
|
4933
4926
|
- createdAt
|
|
4934
4927
|
- updatedAt
|
|
@@ -5076,20 +5069,13 @@ paths:
|
|
|
5076
5069
|
- address
|
|
5077
5070
|
- isPrimary
|
|
5078
5071
|
- externalUserId
|
|
5079
|
-
- status
|
|
5080
|
-
- policy
|
|
5081
|
-
- verifiedAt
|
|
5082
|
-
- verifiedVia
|
|
5083
5072
|
- lastSeenAt
|
|
5084
5073
|
- interactionCount
|
|
5085
5074
|
- lastInteraction
|
|
5086
|
-
- revokedReason
|
|
5087
|
-
- blockedReason
|
|
5088
5075
|
additionalProperties: false
|
|
5089
5076
|
required:
|
|
5090
5077
|
- id
|
|
5091
5078
|
- displayName
|
|
5092
|
-
- role
|
|
5093
5079
|
- interactionCount
|
|
5094
5080
|
- createdAt
|
|
5095
5081
|
- updatedAt
|
|
@@ -5206,20 +5192,13 @@ paths:
|
|
|
5206
5192
|
- address
|
|
5207
5193
|
- isPrimary
|
|
5208
5194
|
- externalUserId
|
|
5209
|
-
- status
|
|
5210
|
-
- policy
|
|
5211
|
-
- verifiedAt
|
|
5212
|
-
- verifiedVia
|
|
5213
5195
|
- lastSeenAt
|
|
5214
5196
|
- interactionCount
|
|
5215
5197
|
- lastInteraction
|
|
5216
|
-
- revokedReason
|
|
5217
|
-
- blockedReason
|
|
5218
5198
|
additionalProperties: false
|
|
5219
5199
|
required:
|
|
5220
5200
|
- id
|
|
5221
5201
|
- displayName
|
|
5222
|
-
- role
|
|
5223
5202
|
- interactionCount
|
|
5224
5203
|
- createdAt
|
|
5225
5204
|
- updatedAt
|
|
@@ -5581,20 +5560,13 @@ paths:
|
|
|
5581
5560
|
- address
|
|
5582
5561
|
- isPrimary
|
|
5583
5562
|
- externalUserId
|
|
5584
|
-
- status
|
|
5585
|
-
- policy
|
|
5586
|
-
- verifiedAt
|
|
5587
|
-
- verifiedVia
|
|
5588
5563
|
- lastSeenAt
|
|
5589
5564
|
- interactionCount
|
|
5590
5565
|
- lastInteraction
|
|
5591
|
-
- revokedReason
|
|
5592
|
-
- blockedReason
|
|
5593
5566
|
additionalProperties: false
|
|
5594
5567
|
required:
|
|
5595
5568
|
- id
|
|
5596
5569
|
- displayName
|
|
5597
|
-
- role
|
|
5598
5570
|
- interactionCount
|
|
5599
5571
|
- createdAt
|
|
5600
5572
|
- updatedAt
|
|
@@ -5776,20 +5748,13 @@ paths:
|
|
|
5776
5748
|
- address
|
|
5777
5749
|
- isPrimary
|
|
5778
5750
|
- externalUserId
|
|
5779
|
-
- status
|
|
5780
|
-
- policy
|
|
5781
|
-
- verifiedAt
|
|
5782
|
-
- verifiedVia
|
|
5783
5751
|
- lastSeenAt
|
|
5784
5752
|
- interactionCount
|
|
5785
5753
|
- lastInteraction
|
|
5786
|
-
- revokedReason
|
|
5787
|
-
- blockedReason
|
|
5788
5754
|
additionalProperties: false
|
|
5789
5755
|
required:
|
|
5790
5756
|
- id
|
|
5791
5757
|
- displayName
|
|
5792
|
-
- role
|
|
5793
5758
|
- interactionCount
|
|
5794
5759
|
- createdAt
|
|
5795
5760
|
- updatedAt
|
package/package.json
CHANGED
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
* and are discovered through the same path as every other channel.
|
|
8
8
|
*
|
|
9
9
|
* This suite verifies that address-based lookup returns the correct
|
|
10
|
-
* `memberRecord` with the right
|
|
10
|
+
* `memberRecord` with the right ACL status so relay-setup-router
|
|
11
11
|
* can emit the appropriate outcome (e.g. `unverified_caller`).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
15
|
|
|
16
|
+
import type {
|
|
17
|
+
ChannelPolicy,
|
|
18
|
+
ChannelStatus,
|
|
19
|
+
ContactRole,
|
|
20
|
+
} from "../contacts/types.js";
|
|
21
|
+
|
|
16
22
|
// ── Logger mock (suppress output) ───────────────────────────────────────────
|
|
17
23
|
mock.module("../util/logger.js", () => ({
|
|
18
24
|
getLogger: () =>
|
|
@@ -23,13 +29,21 @@ mock.module("../util/logger.js", () => ({
|
|
|
23
29
|
let _byAddress: ReturnType<
|
|
24
30
|
(typeof import("../contacts/contact-store.js"))["findContactByAddress"]
|
|
25
31
|
> = null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
// ACL view is carried on memberRecord, sourced from the local ACL columns via
|
|
33
|
+
// getLocalMemberAcl. Stub it per test instead of seeding the DB.
|
|
34
|
+
let _acl: { status: ChannelStatus; policy: ChannelPolicy; role: ContactRole } =
|
|
35
|
+
{ status: "unverified", policy: "allow", role: "contact" };
|
|
29
36
|
|
|
30
37
|
mock.module("../contacts/contact-store.js", () => ({
|
|
31
38
|
findContactByAddress: (_type: string, _addr: string) => _byAddress,
|
|
32
|
-
|
|
39
|
+
getLocalMemberAcl: (_channelId: string) => _acl,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Guardian resolution now reads the gateway delivery cache; these suites only
|
|
43
|
+
// exercise the member/address path, so the cache peek stays empty.
|
|
44
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
45
|
+
peekCachedGuardianDelivery: () => undefined,
|
|
46
|
+
guardianForChannel: () => undefined,
|
|
33
47
|
}));
|
|
34
48
|
|
|
35
49
|
// ── Real import after mocks ───────────────────────────────────────────────────
|
|
@@ -45,11 +59,12 @@ function makeContact(
|
|
|
45
59
|
status: "unverified" | "active" = "unverified",
|
|
46
60
|
): ContactWithChannels {
|
|
47
61
|
const channelId = "ch-test";
|
|
62
|
+
// ACL lives on memberRecord (carrier), sourced from getLocalMemberAcl — set
|
|
63
|
+
// the stub here so the resolver classifies trust off this status/role.
|
|
64
|
+
_acl = { status, policy: "allow", role };
|
|
48
65
|
return {
|
|
49
66
|
id: "contact-test",
|
|
50
67
|
displayName: "Patrick Test",
|
|
51
|
-
role,
|
|
52
|
-
principalId: null,
|
|
53
68
|
notes: null,
|
|
54
69
|
lastInteraction: null,
|
|
55
70
|
interactionCount: 0,
|
|
@@ -65,16 +80,10 @@ function makeContact(
|
|
|
65
80
|
address: PHONE,
|
|
66
81
|
externalChatId: null,
|
|
67
82
|
isPrimary: true,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
verifiedAt: null,
|
|
71
|
-
verifiedVia: null,
|
|
72
|
-
revokedReason: null,
|
|
73
|
-
blockedReason: null,
|
|
83
|
+
inviteId: null,
|
|
84
|
+
lastSeenAt: null,
|
|
74
85
|
interactionCount: 0,
|
|
75
86
|
lastInteraction: null,
|
|
76
|
-
lastSeenAt: null,
|
|
77
|
-
inviteId: null,
|
|
78
87
|
createdAt: 0,
|
|
79
88
|
updatedAt: 0,
|
|
80
89
|
},
|
|
@@ -87,7 +96,7 @@ function makeContact(
|
|
|
87
96
|
describe("resolveActorTrust — address fallback", () => {
|
|
88
97
|
beforeEach(() => {
|
|
89
98
|
_byAddress = null;
|
|
90
|
-
|
|
99
|
+
_acl = { status: "unverified", policy: "allow", role: "contact" };
|
|
91
100
|
});
|
|
92
101
|
|
|
93
102
|
test("finds unverified channel via address when externalUserId is null", () => {
|
|
@@ -103,7 +112,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
103
112
|
|
|
104
113
|
expect(result.memberRecord).not.toBeNull();
|
|
105
114
|
expect(result.memberRecord?.contact.displayName).toBe("Patrick Test");
|
|
106
|
-
expect(result.memberRecord?.
|
|
115
|
+
expect(result.memberRecord?.status).toBe("unverified");
|
|
107
116
|
// trustClass is 'unverified_contact' for a member whose channel is
|
|
108
117
|
// pending or unverified — known to the guardian but not yet verified.
|
|
109
118
|
expect(result.trustClass).toBe("unverified_contact");
|
|
@@ -119,7 +128,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
119
128
|
actorExternalId: PHONE,
|
|
120
129
|
});
|
|
121
130
|
|
|
122
|
-
expect(result.memberRecord?.
|
|
131
|
+
expect(result.memberRecord?.status).toBe("active");
|
|
123
132
|
expect(result.memberRecord?.channel.address).toBe(PHONE);
|
|
124
133
|
});
|
|
125
134
|
|
|
@@ -150,7 +159,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
150
159
|
});
|
|
151
160
|
|
|
152
161
|
expect(result.memberRecord).not.toBeNull();
|
|
153
|
-
expect(result.memberRecord?.
|
|
162
|
+
expect(result.memberRecord?.status).toBe("active");
|
|
154
163
|
expect(result.trustClass).toBe("trusted_contact");
|
|
155
164
|
});
|
|
156
165
|
|
|
@@ -158,8 +167,8 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
158
167
|
// Mirrors the unverified branch but for `pending` status (e.g. a phone
|
|
159
168
|
// contact registered by name-capture awaiting the DTMF challenge).
|
|
160
169
|
const contact = makeContact("contact", "unverified");
|
|
161
|
-
// Override status to "pending" — makeContact only accepts unverified/active
|
|
162
|
-
|
|
170
|
+
// Override ACL status to "pending" — makeContact only accepts unverified/active.
|
|
171
|
+
_acl = { ..._acl, status: "pending" };
|
|
163
172
|
_byAddress = contact;
|
|
164
173
|
|
|
165
174
|
const result = resolveActorTrust({
|
|
@@ -169,15 +178,15 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
169
178
|
actorExternalId: PHONE,
|
|
170
179
|
});
|
|
171
180
|
|
|
172
|
-
expect(result.memberRecord?.
|
|
181
|
+
expect(result.memberRecord?.status).toBe("pending");
|
|
173
182
|
expect(result.trustClass).toBe("unverified_contact");
|
|
174
183
|
});
|
|
175
184
|
|
|
176
185
|
test("blocked-status member is classified as unknown (not unverified_contact)", () => {
|
|
177
186
|
// Hard-deny statuses (blocked, revoked) stay `unknown` — admission-layer
|
|
178
|
-
// re-checks channel
|
|
187
|
+
// re-checks channel status and emits the hard-deny reasons.
|
|
179
188
|
const contact = makeContact("contact", "unverified");
|
|
180
|
-
|
|
189
|
+
_acl = { ..._acl, status: "blocked" };
|
|
181
190
|
_byAddress = contact;
|
|
182
191
|
|
|
183
192
|
const result = resolveActorTrust({
|
|
@@ -187,13 +196,13 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
187
196
|
actorExternalId: PHONE,
|
|
188
197
|
});
|
|
189
198
|
|
|
190
|
-
expect(result.memberRecord?.
|
|
199
|
+
expect(result.memberRecord?.status).toBe("blocked");
|
|
191
200
|
expect(result.trustClass).toBe("unknown");
|
|
192
201
|
});
|
|
193
202
|
|
|
194
203
|
test("revoked-status member is classified as unknown", () => {
|
|
195
204
|
const contact = makeContact("contact", "unverified");
|
|
196
|
-
|
|
205
|
+
_acl = { ..._acl, status: "revoked" };
|
|
197
206
|
_byAddress = contact;
|
|
198
207
|
|
|
199
208
|
const result = resolveActorTrust({
|
|
@@ -203,7 +212,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
203
212
|
actorExternalId: PHONE,
|
|
204
213
|
});
|
|
205
214
|
|
|
206
|
-
expect(result.memberRecord?.
|
|
215
|
+
expect(result.memberRecord?.status).toBe("revoked");
|
|
207
216
|
expect(result.trustClass).toBe("unknown");
|
|
208
217
|
});
|
|
209
218
|
});
|
|
@@ -69,7 +69,6 @@ mock.module("../daemon/approval-generators.js", () => ({
|
|
|
69
69
|
createApprovalConversationGenerator: () => _testApprovalConversationGenerator,
|
|
70
70
|
}));
|
|
71
71
|
|
|
72
|
-
import { upsertContact } from "../contacts/contact-store.js";
|
|
73
72
|
import type { Conversation } from "../daemon/conversation.js";
|
|
74
73
|
import {
|
|
75
74
|
createCanonicalGuardianDelivery,
|
|
@@ -86,7 +85,10 @@ import * as gatewayClient from "../runtime/gateway-client.js";
|
|
|
86
85
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
87
86
|
import { _setTestPollMaxWait } from "../runtime/routes/channel-route-shared.js";
|
|
88
87
|
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
89
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
handleChannelInbound,
|
|
90
|
+
seedContactChannel,
|
|
91
|
+
} from "./helpers/channel-test-adapter.js";
|
|
90
92
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
91
93
|
|
|
92
94
|
await initializeDb();
|
|
@@ -212,22 +214,19 @@ function makeInboundRequest(overrides: Record<string, unknown> = {}): Request {
|
|
|
212
214
|
const noopProcessMessage = mock(async () => ({ messageId: "msg-1" }));
|
|
213
215
|
|
|
214
216
|
function ensureTestContact(): void {
|
|
215
|
-
|
|
217
|
+
seedContactChannel({
|
|
218
|
+
sourceChannel: "telegram",
|
|
219
|
+
externalUserId: "telegram-user-default",
|
|
216
220
|
displayName: "Test User",
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
address: "slack-user-default",
|
|
227
|
-
status: "active",
|
|
228
|
-
policy: "allow",
|
|
229
|
-
},
|
|
230
|
-
],
|
|
221
|
+
status: "active",
|
|
222
|
+
policy: "allow",
|
|
223
|
+
});
|
|
224
|
+
seedContactChannel({
|
|
225
|
+
sourceChannel: "slack",
|
|
226
|
+
externalUserId: "slack-user-default",
|
|
227
|
+
displayName: "Test User",
|
|
228
|
+
status: "active",
|
|
229
|
+
policy: "allow",
|
|
231
230
|
});
|
|
232
231
|
}
|
|
233
232
|
|
|
@@ -1926,16 +1925,12 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
|
|
|
1926
1925
|
guardianDeliveryChatId: "guardian-tc-selfapproval-chat",
|
|
1927
1926
|
guardianPrincipalId: "guardian-tc-selfapproval",
|
|
1928
1927
|
});
|
|
1929
|
-
|
|
1928
|
+
seedContactChannel({
|
|
1929
|
+
sourceChannel: "telegram",
|
|
1930
|
+
externalUserId: "tc-selfapproval-user",
|
|
1930
1931
|
displayName: "TC Self-Approval User",
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
type: "telegram",
|
|
1934
|
-
address: "tc-selfapproval-user",
|
|
1935
|
-
status: "active",
|
|
1936
|
-
policy: "allow",
|
|
1937
|
-
},
|
|
1938
|
-
],
|
|
1932
|
+
status: "active",
|
|
1933
|
+
policy: "allow",
|
|
1939
1934
|
});
|
|
1940
1935
|
});
|
|
1941
1936
|
|
|
@@ -65,45 +65,77 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
65
65
|
|
|
66
66
|
// Gateway relay mock — the revoke path relays the ACL downgrade over IPC and
|
|
67
67
|
// validates the response; return a well-formed mark_channel_revoked result.
|
|
68
|
+
// The gateway owns the revoke and dual-writes the local assistant row to
|
|
69
|
+
// "revoked"; mirror that dual-write here so guardian-resolution reads under
|
|
70
|
+
// test observe the downgrade (the assistant-side teardown is now a no-op shim).
|
|
68
71
|
mock.module("../ipc/gateway-client.js", () => ({
|
|
69
72
|
ipcCallPersistent: async (
|
|
70
|
-
|
|
73
|
+
method: string,
|
|
71
74
|
params?: Record<string, unknown>,
|
|
72
|
-
) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
) => {
|
|
76
|
+
if (method === "mark_channel_revoked") {
|
|
77
|
+
const { getDb } = await import("../memory/db-connection.js");
|
|
78
|
+
const { contactChannels } = await import("../memory/schema.js");
|
|
79
|
+
const { eq } = await import("drizzle-orm");
|
|
80
|
+
const channelId = params?.contactChannelId as string | undefined;
|
|
81
|
+
if (channelId) {
|
|
82
|
+
getDb()
|
|
83
|
+
.update(contactChannels)
|
|
84
|
+
.set({ status: "revoked" })
|
|
85
|
+
.where(eq(contactChannels.id, channelId))
|
|
86
|
+
.run();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
didWrite: true,
|
|
92
|
+
channel: {
|
|
93
|
+
id: (params?.contactChannelId as string) ?? "ch1",
|
|
94
|
+
contactId: "c1",
|
|
95
|
+
type: "phone",
|
|
96
|
+
address: "addr",
|
|
97
|
+
status: "revoked",
|
|
98
|
+
revokedReason: (params?.reason as string) ?? null,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
84
102
|
}));
|
|
85
103
|
|
|
86
104
|
// Guardian-delivery reader mock — the inbound challenge guard reads guardian
|
|
87
105
|
// existence from the gateway. Derive the list from the local binding state so
|
|
88
106
|
// the gateway-backed presence guard mirrors the DB the rest of the test sets up.
|
|
89
107
|
const resolveGuardianList = async (input?: { channelTypes?: string[] }) => {
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
);
|
|
108
|
+
const { getDb } = await import("../memory/db-connection.js");
|
|
109
|
+
const { contacts, contactChannels } = await import("../memory/schema.js");
|
|
110
|
+
const { and, eq } = await import("drizzle-orm");
|
|
93
111
|
const channels = input?.channelTypes ?? [];
|
|
94
112
|
return channels
|
|
95
113
|
.map((channelType) => {
|
|
96
|
-
const
|
|
97
|
-
|
|
114
|
+
const row = getDb()
|
|
115
|
+
.select({ contact: contacts, channel: contactChannels })
|
|
116
|
+
.from(contacts)
|
|
117
|
+
.innerJoin(
|
|
118
|
+
contactChannels,
|
|
119
|
+
eq(contacts.id, contactChannels.contactId),
|
|
120
|
+
)
|
|
121
|
+
.where(
|
|
122
|
+
and(
|
|
123
|
+
eq(contacts.role, "guardian"),
|
|
124
|
+
eq(contactChannels.type, channelType),
|
|
125
|
+
eq(contactChannels.status, "active"),
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
.get();
|
|
129
|
+
if (!row) return null;
|
|
98
130
|
return {
|
|
99
131
|
channelType,
|
|
100
|
-
contactId:
|
|
101
|
-
principalId:
|
|
102
|
-
displayName:
|
|
103
|
-
address:
|
|
104
|
-
externalChatId:
|
|
132
|
+
contactId: row.contact.id,
|
|
133
|
+
principalId: row.contact.principalId ?? null,
|
|
134
|
+
displayName: row.contact.displayName ?? null,
|
|
135
|
+
address: row.channel.address,
|
|
136
|
+
externalChatId: row.channel.externalChatId ?? null,
|
|
105
137
|
status: "active",
|
|
106
|
-
verifiedAt:
|
|
138
|
+
verifiedAt: row.channel.verifiedAt ?? null,
|
|
107
139
|
};
|
|
108
140
|
})
|
|
109
141
|
.filter((g) => g !== null);
|
|
@@ -147,6 +179,7 @@ import {
|
|
|
147
179
|
} from "../memory/guardian-rate-limits.js";
|
|
148
180
|
import {
|
|
149
181
|
channelVerificationSessions,
|
|
182
|
+
contactChannels,
|
|
150
183
|
conversations,
|
|
151
184
|
} from "../memory/schema.js";
|
|
152
185
|
import {
|
|
@@ -192,6 +225,19 @@ function resetTables(): void {
|
|
|
192
225
|
mockBotUsername = "test_bot";
|
|
193
226
|
}
|
|
194
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Revoke a guardian channel's local ACL state directly. The production revoke
|
|
230
|
+
* is gateway-owned (relayed via mark_channel_revoked); this stamps the local
|
|
231
|
+
* mirror so the guardian-resolution reads still under test see the downgrade.
|
|
232
|
+
*/
|
|
233
|
+
function revokeGuardianChannelLocally(channelType: string): void {
|
|
234
|
+
getDb()
|
|
235
|
+
.update(contactChannels)
|
|
236
|
+
.set({ status: "revoked" })
|
|
237
|
+
.where(eq(contactChannels.type, channelType))
|
|
238
|
+
.run();
|
|
239
|
+
}
|
|
240
|
+
|
|
195
241
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
196
242
|
// 2. Verification Challenge Lifecycle (Store)
|
|
197
243
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -558,7 +604,7 @@ describe("guardian identity check", () => {
|
|
|
558
604
|
guardianDeliveryChatId: "chat-42",
|
|
559
605
|
});
|
|
560
606
|
|
|
561
|
-
|
|
607
|
+
revokeGuardianChannelLocally("telegram");
|
|
562
608
|
|
|
563
609
|
expect(await isGuardian("asst-1", "telegram", "user-42")).toBe(false);
|
|
564
610
|
});
|
|
@@ -595,7 +641,7 @@ describe("guardian identity check", () => {
|
|
|
595
641
|
expect(await isGuardian("asst-1", "telegram", "phone-user-1")).toBe(false);
|
|
596
642
|
});
|
|
597
643
|
|
|
598
|
-
test("
|
|
644
|
+
test("guardian binding read reflects a gateway-owned revoke", async () => {
|
|
599
645
|
createGuardianBinding({
|
|
600
646
|
channel: "telegram",
|
|
601
647
|
guardianExternalUserId: "user-42",
|
|
@@ -603,8 +649,10 @@ describe("guardian identity check", () => {
|
|
|
603
649
|
guardianDeliveryChatId: "chat-42",
|
|
604
650
|
});
|
|
605
651
|
|
|
606
|
-
|
|
607
|
-
|
|
652
|
+
// The revoke is gateway-owned; serviceRevokeBinding's local teardown is a
|
|
653
|
+
// no-op shim. Stamp the local downgrade and assert the read reflects it.
|
|
654
|
+
serviceRevokeBinding("asst-1", "telegram");
|
|
655
|
+
revokeGuardianChannelLocally("telegram");
|
|
608
656
|
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
609
657
|
});
|
|
610
658
|
});
|
|
@@ -958,7 +1006,7 @@ describe("channel-scoped guardian resolution", () => {
|
|
|
958
1006
|
guardianDeliveryChatId: "chat-beta",
|
|
959
1007
|
});
|
|
960
1008
|
|
|
961
|
-
|
|
1009
|
+
revokeGuardianChannelLocally("telegram");
|
|
962
1010
|
|
|
963
1011
|
expect(await getGuardianBinding("self", "telegram")).toBeNull();
|
|
964
1012
|
expect(await getGuardianBinding("self", "phone")).not.toBeNull();
|
|
@@ -1472,8 +1520,10 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1472
1520
|
guardianDeliveryChatId: "voice-chat-1",
|
|
1473
1521
|
});
|
|
1474
1522
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1523
|
+
// The revoke is gateway-owned; serviceRevokeBinding's local teardown is a
|
|
1524
|
+
// no-op shim. Stamp the local downgrade and assert the read reflects it.
|
|
1525
|
+
serviceRevokeBinding("asst-1", "phone");
|
|
1526
|
+
revokeGuardianChannelLocally("phone");
|
|
1477
1527
|
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
1478
1528
|
});
|
|
1479
1529
|
|
|
@@ -1491,7 +1541,7 @@ describe("voice guardian identity and revocation", () => {
|
|
|
1491
1541
|
guardianDeliveryChatId: "tg-chat-1",
|
|
1492
1542
|
});
|
|
1493
1543
|
|
|
1494
|
-
|
|
1544
|
+
revokeGuardianChannelLocally("phone");
|
|
1495
1545
|
|
|
1496
1546
|
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
1497
1547
|
expect(await getGuardianBinding("asst-1", "telegram")).not.toBeNull();
|
|
@@ -59,7 +59,6 @@ mock.module("../daemon/disk-pressure-guard.js", () => ({
|
|
|
59
59
|
diskPressureStatusSequence?.shift() ?? diskPressureStatus,
|
|
60
60
|
}));
|
|
61
61
|
|
|
62
|
-
import { upsertContact } from "../contacts/contact-store.js";
|
|
63
62
|
import { getDb } from "../memory/db-connection.js";
|
|
64
63
|
import { initializeDb } from "../memory/db-init.js";
|
|
65
64
|
import * as deliveryCrud from "../memory/delivery-crud.js";
|
|
@@ -71,6 +70,7 @@ import {
|
|
|
71
70
|
import { sweepFailedEvents } from "../runtime/channel-retry-sweep.js";
|
|
72
71
|
import {
|
|
73
72
|
handleChannelInbound,
|
|
73
|
+
seedContactChannel,
|
|
74
74
|
setAdapterProcessMessage,
|
|
75
75
|
} from "./helpers/channel-test-adapter.js";
|
|
76
76
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
@@ -90,16 +90,12 @@ function resetTables(): void {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function seedTrustedContact(policy: "allow" | "escalate" = "allow"): void {
|
|
93
|
-
|
|
93
|
+
seedContactChannel({
|
|
94
|
+
sourceChannel: "telegram",
|
|
95
|
+
externalUserId: "telegram-user-1",
|
|
94
96
|
displayName: "Example User",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
type: "telegram",
|
|
98
|
-
address: "telegram-user-1",
|
|
99
|
-
status: "active",
|
|
100
|
-
policy,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
97
|
+
status: "active",
|
|
98
|
+
policy,
|
|
103
99
|
});
|
|
104
100
|
}
|
|
105
101
|
|
|
@@ -236,16 +232,12 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
236
232
|
});
|
|
237
233
|
|
|
238
234
|
test("blocks non-guardian Slack reactions silently (no reply) before persistence while locked", async () => {
|
|
239
|
-
|
|
235
|
+
seedContactChannel({
|
|
236
|
+
sourceChannel: "slack",
|
|
237
|
+
externalUserId: "slack-user-1",
|
|
240
238
|
displayName: "Example Slack User",
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
type: "slack",
|
|
244
|
-
address: "slack-user-1",
|
|
245
|
-
status: "active",
|
|
246
|
-
policy: "allow",
|
|
247
|
-
},
|
|
248
|
-
],
|
|
239
|
+
status: "active",
|
|
240
|
+
policy: "allow",
|
|
249
241
|
});
|
|
250
242
|
const processMessage = mock(async () => {
|
|
251
243
|
throw new Error("processMessage should not run");
|