@vellumai/assistant 0.4.33 → 0.4.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -3
- package/src/__tests__/actor-token-service.test.ts +4 -11
- package/src/__tests__/approval-primitive.test.ts +0 -45
- package/src/__tests__/assistant-id-boundary-guard.test.ts +150 -0
- package/src/__tests__/callback-handoff-copy.test.ts +0 -1
- package/src/__tests__/channel-approval-routes.test.ts +5 -45
- package/src/__tests__/channel-guardian.test.ts +122 -345
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -3
- package/src/__tests__/contacts-tools.test.ts +4 -5
- package/src/__tests__/conversation-attention-store.test.ts +2 -65
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -2
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -7
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -74
- package/src/__tests__/guardian-action-late-reply.test.ts +1 -8
- package/src/__tests__/guardian-grant-minting.test.ts +0 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -3
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +0 -7
- package/src/__tests__/notification-broadcaster.test.ts +1 -2
- package/src/__tests__/notification-decision-fallback.test.ts +0 -2
- package/src/__tests__/notification-decision-strategy.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +11 -83
- package/src/__tests__/scoped-approval-grants.test.ts +9 -40
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -36
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/send-notification-tool.test.ts +0 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -4
- package/src/__tests__/thread-seed-composer.test.ts +0 -1
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -4
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -5
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -17
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -13
- package/src/__tests__/trusted-contact-verification.test.ts +3 -15
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -37
- package/src/approvals/approval-primitive.ts +0 -15
- package/src/approvals/guardian-decision-primitive.ts +0 -3
- package/src/approvals/guardian-request-resolvers.ts +0 -5
- package/src/calls/call-domain.ts +0 -3
- package/src/calls/call-store.ts +0 -3
- package/src/calls/guardian-action-sweep.ts +2 -1
- package/src/calls/guardian-dispatch.ts +1 -2
- package/src/calls/relay-access-wait.ts +0 -4
- package/src/calls/relay-server.ts +3 -11
- package/src/calls/relay-setup-router.ts +1 -2
- package/src/calls/relay-verification.ts +0 -1
- package/src/calls/twilio-routes.ts +0 -3
- package/src/calls/types.ts +0 -1
- package/src/calls/voice-session-bridge.ts +0 -1
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -1
- package/src/contacts/contact-store.ts +13 -88
- package/src/contacts/contacts-write.ts +3 -11
- package/src/contacts/types.ts +0 -1
- package/src/daemon/handlers/config-channels.ts +16 -42
- package/src/daemon/handlers/config-inbox.ts +6 -6
- package/src/daemon/handlers/contacts.ts +3 -11
- package/src/daemon/handlers/index.ts +0 -2
- package/src/daemon/session-process.ts +0 -4
- package/src/memory/conversation-attention-store.ts +4 -19
- package/src/memory/conversation-crud.ts +0 -2
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +0 -12
- package/src/memory/guardian-approvals.ts +35 -80
- package/src/memory/guardian-rate-limits.ts +1 -14
- package/src/memory/guardian-verification.ts +6 -34
- package/src/memory/invite-store.ts +5 -14
- package/src/memory/migrations/134-contacts-notes-column.ts +64 -45
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +263 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +14 -1
- package/src/memory/schema/calls.ts +0 -7
- package/src/memory/schema/contacts.ts +0 -8
- package/src/memory/schema/guardian.ts +0 -5
- package/src/memory/schema/infrastructure.ts +0 -2
- package/src/memory/schema/notifications.ts +3 -17
- package/src/memory/scoped-approval-grants.ts +2 -24
- package/src/notifications/adapters/sms.ts +2 -1
- package/src/notifications/broadcaster.ts +1 -6
- package/src/notifications/decision-engine.ts +3 -4
- package/src/notifications/deliveries-store.ts +0 -4
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/deterministic-checks.ts +1 -6
- package/src/notifications/emit-signal.ts +4 -11
- package/src/notifications/events-store.ts +7 -17
- package/src/notifications/preference-summary.ts +2 -2
- package/src/notifications/preferences-store.ts +2 -9
- package/src/notifications/signal.ts +0 -1
- package/src/notifications/thread-candidates.ts +1 -11
- package/src/notifications/types.ts +0 -3
- package/src/runtime/access-request-helper.ts +3 -10
- package/src/runtime/actor-refresh-token-store.ts +0 -6
- package/src/runtime/actor-token-store.ts +3 -16
- package/src/runtime/actor-trust-resolver.ts +1 -4
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -9
- package/src/runtime/auth/credential-service.ts +1 -15
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/channel-guardian-service.ts +15 -46
- package/src/runtime/channel-invite-transport.ts +8 -0
- package/src/runtime/channel-invite-transports/email.ts +4 -0
- package/src/runtime/channel-invite-transports/slack.ts +6 -0
- package/src/runtime/channel-invite-transports/sms.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +0 -1
- package/src/runtime/guardian-action-followup-executor.ts +3 -2
- package/src/runtime/guardian-action-grant-minter.ts +0 -1
- package/src/runtime/guardian-outbound-actions.ts +2 -12
- package/src/runtime/guardian-vellum-migration.ts +2 -3
- package/src/runtime/http-server.ts +0 -1
- package/src/runtime/invite-redemption-service.ts +1 -14
- package/src/runtime/local-actor-identity.ts +2 -5
- package/src/runtime/routes/access-request-decision.ts +0 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -9
- package/src/runtime/routes/channel-readiness-routes.ts +29 -18
- package/src/runtime/routes/contact-routes.ts +15 -40
- package/src/runtime/routes/conversation-attention-routes.ts +0 -2
- package/src/runtime/routes/global-search-routes.ts +0 -2
- package/src/runtime/routes/guardian-bootstrap-routes.ts +5 -6
- package/src/runtime/routes/guardian-expiry-sweep.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +0 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -43
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -4
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -1
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +0 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -7
- package/src/runtime/routes/pairing-routes.ts +4 -4
- package/src/runtime/tool-grant-request-helper.ts +0 -1
- package/src/tools/browser/browser-manager.ts +22 -21
- package/src/tools/browser/runtime-check.ts +111 -6
- package/src/tools/calls/call-start.ts +1 -3
- package/src/tools/followups/followup_create.ts +1 -2
- package/src/tools/tool-approval-handler.ts +0 -2
|
@@ -1,68 +1,87 @@
|
|
|
1
1
|
import { getLogger } from "../../util/logger.js";
|
|
2
2
|
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
3
4
|
|
|
4
5
|
const log = getLogger("migration-134");
|
|
5
6
|
|
|
6
7
|
export function migrateContactsNotesColumn(database: DrizzleDb): void {
|
|
7
|
-
|
|
8
|
+
withCrashRecovery(database, "migration_contacts_notes_column_v1", () => {
|
|
9
|
+
const raw = getSqliteFrom(database);
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
try {
|
|
12
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts ADD COLUMN notes TEXT`);
|
|
13
|
+
} catch {
|
|
14
|
+
/* already exists */
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check which legacy columns still exist — handles partial completion
|
|
18
|
+
// if a previous run crashed after dropping some columns but not all.
|
|
19
|
+
const cols = new Set(
|
|
20
|
+
(
|
|
21
|
+
raw.query(`PRAGMA table_info(contacts)`).all() as Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
}>
|
|
24
|
+
).map((c) => c.name),
|
|
25
|
+
);
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
const legacyCols = [
|
|
28
|
+
"relationship",
|
|
29
|
+
"importance",
|
|
30
|
+
"response_expectation",
|
|
31
|
+
"preferred_tone",
|
|
32
|
+
] as const;
|
|
33
|
+
const remaining = legacyCols.filter((c) => cols.has(c));
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
// Backfill notes from legacy columns if any are still present and notes
|
|
36
|
+
// haven't been populated yet (only run once, before any columns are dropped).
|
|
37
|
+
if (remaining.length === legacyCols.length) {
|
|
38
|
+
const rows = raw
|
|
39
|
+
.query(
|
|
40
|
+
`SELECT id, relationship, importance, response_expectation, preferred_tone
|
|
26
41
|
FROM contacts
|
|
27
42
|
WHERE relationship IS NOT NULL
|
|
28
43
|
OR importance != 0.5
|
|
29
44
|
OR response_expectation IS NOT NULL
|
|
30
45
|
OR preferred_tone IS NOT NULL`,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
)
|
|
47
|
+
.all() as Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
relationship: string | null;
|
|
50
|
+
importance: number;
|
|
51
|
+
response_expectation: string | null;
|
|
52
|
+
preferred_tone: string | null;
|
|
53
|
+
}>;
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
const update = raw.prepare(`UPDATE contacts SET notes = ? WHERE id = ?`);
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
const parts: string[] = [];
|
|
59
|
+
if (row.relationship) parts.push(`Relationship: ${row.relationship}`);
|
|
60
|
+
if (row.importance !== 0.5) parts.push(`Importance: ${row.importance}`);
|
|
61
|
+
if (row.response_expectation)
|
|
62
|
+
parts.push(`Response expectation: ${row.response_expectation}`);
|
|
63
|
+
if (row.preferred_tone)
|
|
64
|
+
parts.push(`Preferred tone: ${row.preferred_tone}`);
|
|
65
|
+
if (parts.length > 0) {
|
|
66
|
+
update.run(parts.join("\n"), row.id);
|
|
67
|
+
}
|
|
52
68
|
}
|
|
53
|
-
}
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
const migrated = rows.length;
|
|
71
|
+
if (migrated > 0) {
|
|
72
|
+
log.info({ migrated }, "Migrated contact metadata to notes field");
|
|
73
|
+
}
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
// Drop indexes that reference columns we're about to remove
|
|
76
|
+
// Drop indexes that reference columns we're about to remove.
|
|
77
|
+
// Must happen before the column drops — SQLite rejects dropping a column
|
|
78
|
+
// that is still referenced by an index.
|
|
61
79
|
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_contacts_importance`);
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
// Drop each legacy column individually so partial completion is safe:
|
|
82
|
+
// on crash recovery the loop picks up only the columns that remain.
|
|
83
|
+
for (const col of remaining) {
|
|
84
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts DROP COLUMN ${col}`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
68
87
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
4
|
+
|
|
5
|
+
const log = getLogger("migration-136");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Drop `assistant_id` columns from all 16 daemon tables that carried the
|
|
9
|
+
* per-assistant scoping column. After wave-1 PRs normalised every value to
|
|
10
|
+
* 'self' (the implicit single-tenant identity), the column is dead weight.
|
|
11
|
+
*
|
|
12
|
+
* Steps:
|
|
13
|
+
* 1. Safety assertion: verify all rows are 'self' or NULL.
|
|
14
|
+
* 2. Drop composite indexes that include `assistant_id`.
|
|
15
|
+
* 3. `ALTER TABLE ... DROP COLUMN assistant_id` for each table.
|
|
16
|
+
* 4. Recreate indexes without the `assistant_id` column.
|
|
17
|
+
*/
|
|
18
|
+
export function migrateDropAssistantIdColumns(database: DrizzleDb): void {
|
|
19
|
+
withCrashRecovery(database, "migration_drop_assistant_id_columns_v1", () => {
|
|
20
|
+
const raw = getSqliteFrom(database);
|
|
21
|
+
|
|
22
|
+
// The 16 tables that carry assistant_id.
|
|
23
|
+
const tables = [
|
|
24
|
+
"contacts",
|
|
25
|
+
"assistant_ingress_invites",
|
|
26
|
+
"assistant_inbox_thread_state",
|
|
27
|
+
"call_sessions",
|
|
28
|
+
"channel_guardian_verification_challenges",
|
|
29
|
+
"channel_guardian_approval_requests",
|
|
30
|
+
"channel_guardian_rate_limits",
|
|
31
|
+
"guardian_action_requests",
|
|
32
|
+
"scoped_approval_grants",
|
|
33
|
+
"notification_events",
|
|
34
|
+
"notification_preferences",
|
|
35
|
+
"notification_deliveries",
|
|
36
|
+
"conversation_attention_events",
|
|
37
|
+
"conversation_assistant_attention_state",
|
|
38
|
+
"actor_token_records",
|
|
39
|
+
"actor_refresh_token_records",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// --- Safety assertion ---
|
|
43
|
+
// Verify all existing assistant_id values are 'self' or NULL before dropping.
|
|
44
|
+
for (const table of tables) {
|
|
45
|
+
const cols = new Set(
|
|
46
|
+
(
|
|
47
|
+
raw.query(`PRAGMA table_info(${table})`).all() as Array<{
|
|
48
|
+
name: string;
|
|
49
|
+
}>
|
|
50
|
+
).map((c) => c.name),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!cols.has("assistant_id")) {
|
|
54
|
+
log.info(
|
|
55
|
+
{ table },
|
|
56
|
+
"Table does not have assistant_id column — skipping",
|
|
57
|
+
);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const unexpected = raw
|
|
62
|
+
.query(
|
|
63
|
+
`SELECT DISTINCT assistant_id FROM ${table} WHERE assistant_id IS NOT NULL AND assistant_id != 'self'`,
|
|
64
|
+
)
|
|
65
|
+
.all() as Array<{ assistant_id: string }>;
|
|
66
|
+
|
|
67
|
+
if (unexpected.length > 0) {
|
|
68
|
+
log.warn(
|
|
69
|
+
{ table, values: unexpected.map((r) => r.assistant_id) },
|
|
70
|
+
"Unexpected assistant_id values found — skipping table",
|
|
71
|
+
);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Drop ALL indexes that include assistant_id ---
|
|
77
|
+
// Every index below references the assistant_id column. SQLite will error
|
|
78
|
+
// on ALTER TABLE ... DROP COLUMN if any index still references the column.
|
|
79
|
+
|
|
80
|
+
// channel_guardian_verification_challenges indexes (migrations 110, 026, 027a)
|
|
81
|
+
raw.exec(
|
|
82
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_channel_guardian_challenges_lookup`,
|
|
83
|
+
);
|
|
84
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_guardian_sessions_active`);
|
|
85
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_guardian_sessions_identity`);
|
|
86
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_guardian_sessions_destination`);
|
|
87
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_guardian_sessions_bootstrap`);
|
|
88
|
+
|
|
89
|
+
// channel_guardian_rate_limits indexes (migration 110)
|
|
90
|
+
raw.exec(
|
|
91
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_channel_guardian_rate_limits_actor`,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// assistant_ingress_invites indexes (migration 112)
|
|
95
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_ingress_invites_channel_status`);
|
|
96
|
+
raw.exec(
|
|
97
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_ingress_invites_channel_created`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// assistant_inbox_thread_state indexes (migration 112)
|
|
101
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_inbox_thread_state_channel`);
|
|
102
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_inbox_thread_state_last_msg`);
|
|
103
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_inbox_thread_state_escalation`);
|
|
104
|
+
|
|
105
|
+
// notification_preferences indexes (migration 114)
|
|
106
|
+
raw.exec(
|
|
107
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_notification_preferences_assistant_id`,
|
|
108
|
+
);
|
|
109
|
+
raw.exec(
|
|
110
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_notification_preferences_assistant_priority`,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// notification_events indexes (migration 114)
|
|
114
|
+
raw.exec(
|
|
115
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_notification_events_assistant_event_created`,
|
|
116
|
+
);
|
|
117
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_notification_events_dedupe`);
|
|
118
|
+
|
|
119
|
+
// notification_deliveries indexes (migration 114)
|
|
120
|
+
raw.exec(
|
|
121
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_notification_deliveries_assistant_status`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// conversation_attention_events indexes (migration 117)
|
|
125
|
+
raw.exec(
|
|
126
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_conv_attn_events_assistant_observed`,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// conversation_assistant_attention_state indexes (migration 117)
|
|
130
|
+
raw.exec(
|
|
131
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_conv_attn_state_assistant_latest_msg`,
|
|
132
|
+
);
|
|
133
|
+
raw.exec(
|
|
134
|
+
/*sql*/ `DROP INDEX IF EXISTS idx_conv_attn_state_assistant_last_seen`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// actor_token_records indexes (migration 038)
|
|
138
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_actor_tokens_active_device`);
|
|
139
|
+
|
|
140
|
+
// actor_refresh_token_records indexes (migration 039)
|
|
141
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_refresh_tokens_active_device`);
|
|
142
|
+
|
|
143
|
+
// --- Drop assistant_id column from each table ---
|
|
144
|
+
for (const table of tables) {
|
|
145
|
+
const cols = new Set(
|
|
146
|
+
(
|
|
147
|
+
raw.query(`PRAGMA table_info(${table})`).all() as Array<{
|
|
148
|
+
name: string;
|
|
149
|
+
}>
|
|
150
|
+
).map((c) => c.name),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (!cols.has("assistant_id")) continue;
|
|
154
|
+
|
|
155
|
+
// Re-verify safety before each drop
|
|
156
|
+
const unexpected = raw
|
|
157
|
+
.query(
|
|
158
|
+
`SELECT DISTINCT assistant_id FROM ${table} WHERE assistant_id IS NOT NULL AND assistant_id != 'self'`,
|
|
159
|
+
)
|
|
160
|
+
.all() as Array<{ assistant_id: string }>;
|
|
161
|
+
|
|
162
|
+
if (unexpected.length > 0) {
|
|
163
|
+
log.warn(
|
|
164
|
+
{ table, values: unexpected.map((r) => r.assistant_id) },
|
|
165
|
+
"Unexpected assistant_id values — skipping column drop",
|
|
166
|
+
);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
raw.exec(/*sql*/ `ALTER TABLE ${table} DROP COLUMN assistant_id`);
|
|
171
|
+
log.info({ table }, "Dropped assistant_id column");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Recreate indexes without assistant_id ---
|
|
175
|
+
// Each index below is the equivalent of the dropped index but with the
|
|
176
|
+
// assistant_id column removed. Index names are updated to avoid
|
|
177
|
+
// collisions with the old names.
|
|
178
|
+
|
|
179
|
+
// channel_guardian_verification_challenges
|
|
180
|
+
raw.exec(
|
|
181
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_guardian_challenges_lookup ON channel_guardian_verification_challenges(channel, challenge_hash, status)`,
|
|
182
|
+
);
|
|
183
|
+
raw.exec(
|
|
184
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_guardian_sessions_active ON channel_guardian_verification_challenges(channel, status)`,
|
|
185
|
+
);
|
|
186
|
+
raw.exec(
|
|
187
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_guardian_sessions_identity ON channel_guardian_verification_challenges(channel, expected_external_user_id, expected_chat_id, status)`,
|
|
188
|
+
);
|
|
189
|
+
raw.exec(
|
|
190
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_guardian_sessions_destination ON channel_guardian_verification_challenges(channel, destination_address)`,
|
|
191
|
+
);
|
|
192
|
+
raw.exec(
|
|
193
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_guardian_sessions_bootstrap ON channel_guardian_verification_challenges(channel, bootstrap_token_hash, status)`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// channel_guardian_rate_limits
|
|
197
|
+
raw.exec(
|
|
198
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_guardian_rate_limits_actor ON channel_guardian_rate_limits(channel, actor_external_user_id, actor_chat_id)`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// assistant_ingress_invites
|
|
202
|
+
raw.exec(
|
|
203
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_ingress_invites_channel_status ON assistant_ingress_invites(source_channel, status, expires_at)`,
|
|
204
|
+
);
|
|
205
|
+
raw.exec(
|
|
206
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_ingress_invites_channel_created ON assistant_ingress_invites(source_channel, created_at)`,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// assistant_inbox_thread_state
|
|
210
|
+
raw.exec(
|
|
211
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_thread_state_channel ON assistant_inbox_thread_state(source_channel, external_chat_id)`,
|
|
212
|
+
);
|
|
213
|
+
raw.exec(
|
|
214
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_inbox_thread_state_last_msg ON assistant_inbox_thread_state(last_message_at)`,
|
|
215
|
+
);
|
|
216
|
+
raw.exec(
|
|
217
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_inbox_thread_state_escalation ON assistant_inbox_thread_state(has_pending_escalation, last_message_at)`,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// notification_preferences
|
|
221
|
+
raw.exec(
|
|
222
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_notification_preferences_priority ON notification_preferences(priority DESC)`,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// notification_events
|
|
226
|
+
raw.exec(
|
|
227
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_notification_events_event_created ON notification_events(source_event_name, created_at)`,
|
|
228
|
+
);
|
|
229
|
+
raw.exec(
|
|
230
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_notification_events_dedupe ON notification_events(dedupe_key) WHERE dedupe_key IS NOT NULL`,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// notification_deliveries
|
|
234
|
+
raw.exec(
|
|
235
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_notification_deliveries_status ON notification_deliveries(status)`,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// conversation_attention_events
|
|
239
|
+
raw.exec(
|
|
240
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conv_attn_events_observed ON conversation_attention_events(observed_at)`,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// conversation_assistant_attention_state
|
|
244
|
+
raw.exec(
|
|
245
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conv_attn_state_latest_msg ON conversation_assistant_attention_state(latest_assistant_message_at)`,
|
|
246
|
+
);
|
|
247
|
+
raw.exec(
|
|
248
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conv_attn_state_last_seen ON conversation_assistant_attention_state(last_seen_assistant_message_at)`,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// actor_token_records
|
|
252
|
+
raw.exec(
|
|
253
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_actor_tokens_active_device ON actor_token_records(guardian_principal_id, hashed_device_id) WHERE status = 'active'`,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// actor_refresh_token_records
|
|
257
|
+
raw.exec(
|
|
258
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_refresh_tokens_active_device ON actor_refresh_token_records(guardian_principal_id, hashed_device_id) WHERE status = 'active'`,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
log.info("Completed dropping assistant_id columns from all tables");
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -78,6 +78,7 @@ export { migrateContactsAssistantId } from "./132-contacts-assistant-id.js";
|
|
|
78
78
|
export { migrateAssistantContactMetadata } from "./133-assistant-contact-metadata.js";
|
|
79
79
|
export { migrateContactsNotesColumn } from "./134-contacts-notes-column.js";
|
|
80
80
|
export { migrateBackfillContactInteractionStats } from "./135-backfill-contact-interaction-stats.js";
|
|
81
|
+
export { migrateDropAssistantIdColumns } from "./136-drop-assistant-id-columns.js";
|
|
81
82
|
export {
|
|
82
83
|
MIGRATION_REGISTRY,
|
|
83
84
|
type MigrationRegistryEntry,
|
|
@@ -123,11 +123,24 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
123
123
|
"Enforce NOT NULL on channel_guardian_bindings.guardian_principal_id after backfill",
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
|
-
key: "
|
|
126
|
+
key: "migration_contacts_notes_column_v1",
|
|
127
127
|
version: 17,
|
|
128
|
+
description:
|
|
129
|
+
"Consolidate relationship/importance/response_expectation/preferred_tone into a single notes TEXT column, then drop the legacy columns",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
key: "backfill_contact_interaction_stats",
|
|
133
|
+
version: 18,
|
|
128
134
|
description:
|
|
129
135
|
"Backfill contacts.last_interaction from the max lastSeenAt across each contact's channels",
|
|
130
136
|
},
|
|
137
|
+
{
|
|
138
|
+
key: "migration_drop_assistant_id_columns_v1",
|
|
139
|
+
version: 19,
|
|
140
|
+
dependsOn: ["migration_normalize_assistant_id_to_self_v1"],
|
|
141
|
+
description:
|
|
142
|
+
"Drop assistant_id columns from all 16 daemon tables after normalization to single-tenant identity",
|
|
143
|
+
},
|
|
131
144
|
];
|
|
132
145
|
|
|
133
146
|
export interface MigrationValidationResult {
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
text,
|
|
7
7
|
} from "drizzle-orm/sqlite-core";
|
|
8
8
|
|
|
9
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
10
9
|
import { conversations } from "./conversations.js";
|
|
11
10
|
|
|
12
11
|
export const callSessions = sqliteTable(
|
|
@@ -26,7 +25,6 @@ export const callSessions = sqliteTable(
|
|
|
26
25
|
guardianVerificationSessionId: text("guardian_verification_session_id"),
|
|
27
26
|
callerIdentityMode: text("caller_identity_mode"),
|
|
28
27
|
callerIdentitySource: text("caller_identity_source"),
|
|
29
|
-
assistantId: text("assistant_id"),
|
|
30
28
|
initiatedFromConversationId: text("initiated_from_conversation_id"),
|
|
31
29
|
startedAt: integer("started_at"),
|
|
32
30
|
endedAt: integer("ended_at"),
|
|
@@ -91,7 +89,6 @@ export const channelGuardianVerificationChallenges = sqliteTable(
|
|
|
91
89
|
"channel_guardian_verification_challenges",
|
|
92
90
|
{
|
|
93
91
|
id: text("id").primaryKey(),
|
|
94
|
-
assistantId: text("assistant_id").notNull(),
|
|
95
92
|
channel: text("channel").notNull(),
|
|
96
93
|
challengeHash: text("challenge_hash").notNull(),
|
|
97
94
|
expiresAt: integer("expires_at").notNull(),
|
|
@@ -128,9 +125,6 @@ export const channelGuardianApprovalRequests = sqliteTable(
|
|
|
128
125
|
runId: text("run_id").notNull(),
|
|
129
126
|
requestId: text("request_id"),
|
|
130
127
|
conversationId: text("conversation_id").notNull(),
|
|
131
|
-
assistantId: text("assistant_id")
|
|
132
|
-
.notNull()
|
|
133
|
-
.default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
134
128
|
channel: text("channel").notNull(),
|
|
135
129
|
requesterExternalUserId: text("requester_external_user_id").notNull(),
|
|
136
130
|
requesterChatId: text("requester_chat_id").notNull(),
|
|
@@ -151,7 +145,6 @@ export const channelGuardianRateLimits = sqliteTable(
|
|
|
151
145
|
"channel_guardian_rate_limits",
|
|
152
146
|
{
|
|
153
147
|
id: text("id").primaryKey(),
|
|
154
|
-
assistantId: text("assistant_id").notNull(),
|
|
155
148
|
channel: text("channel").notNull(),
|
|
156
149
|
actorExternalUserId: text("actor_external_user_id").notNull(),
|
|
157
150
|
actorChatId: text("actor_chat_id").notNull(),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
2
|
|
|
3
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
4
3
|
import { conversations } from "./conversations.js";
|
|
5
4
|
|
|
6
5
|
export const contacts = sqliteTable("contacts", {
|
|
@@ -13,7 +12,6 @@ export const contacts = sqliteTable("contacts", {
|
|
|
13
12
|
updatedAt: integer("updated_at").notNull(),
|
|
14
13
|
role: text("role").notNull().default("contact"), // 'guardian' | 'contact'
|
|
15
14
|
principalId: text("principal_id"), // internal auth principal (nullable)
|
|
16
|
-
assistantId: text("assistant_id"), // which assistant this guardian is for (nullable, daemon default is DAEMON_INTERNAL_ASSISTANT_ID)
|
|
17
15
|
contactType: text("contact_type").notNull().default("human"), // 'human' | 'assistant'
|
|
18
16
|
});
|
|
19
17
|
|
|
@@ -69,9 +67,6 @@ export const assistantIngressInvites = sqliteTable(
|
|
|
69
67
|
"assistant_ingress_invites",
|
|
70
68
|
{
|
|
71
69
|
id: text("id").primaryKey(),
|
|
72
|
-
assistantId: text("assistant_id")
|
|
73
|
-
.notNull()
|
|
74
|
-
.default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
75
70
|
sourceChannel: text("source_channel").notNull(),
|
|
76
71
|
tokenHash: text("token_hash").notNull(),
|
|
77
72
|
createdBySessionId: text("created_by_session_id"),
|
|
@@ -103,9 +98,6 @@ export const assistantInboxThreadState = sqliteTable(
|
|
|
103
98
|
conversationId: text("conversation_id")
|
|
104
99
|
.primaryKey()
|
|
105
100
|
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
106
|
-
assistantId: text("assistant_id")
|
|
107
|
-
.notNull()
|
|
108
|
-
.default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
109
101
|
sourceChannel: text("source_channel").notNull(),
|
|
110
102
|
externalChatId: text("external_chat_id").notNull(),
|
|
111
103
|
externalUserId: text("external_user_id"),
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
2
|
|
|
3
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
4
3
|
import { callPendingQuestions, callSessions } from "./calls.js";
|
|
5
4
|
|
|
6
5
|
export const guardianActionRequests = sqliteTable(
|
|
7
6
|
"guardian_action_requests",
|
|
8
7
|
{
|
|
9
8
|
id: text("id").primaryKey(),
|
|
10
|
-
assistantId: text("assistant_id")
|
|
11
|
-
.notNull()
|
|
12
|
-
.default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
13
9
|
kind: text("kind").notNull(), // 'ask_guardian'
|
|
14
10
|
sourceChannel: text("source_channel").notNull(), // 'voice'
|
|
15
11
|
sourceConversationId: text("source_conversation_id").notNull(),
|
|
@@ -145,7 +141,6 @@ export const scopedApprovalGrants = sqliteTable(
|
|
|
145
141
|
"scoped_approval_grants",
|
|
146
142
|
{
|
|
147
143
|
id: text("id").primaryKey(),
|
|
148
|
-
assistantId: text("assistant_id").notNull(),
|
|
149
144
|
scopeMode: text("scope_mode").notNull(), // 'request_id' | 'tool_signature'
|
|
150
145
|
requestId: text("request_id"),
|
|
151
146
|
toolName: text("tool_name"),
|
|
@@ -173,7 +173,6 @@ export const llmUsageEvents = sqliteTable(
|
|
|
173
173
|
export const actorTokenRecords = sqliteTable("actor_token_records", {
|
|
174
174
|
id: text("id").primaryKey(),
|
|
175
175
|
tokenHash: text("token_hash").notNull(),
|
|
176
|
-
assistantId: text("assistant_id").notNull(),
|
|
177
176
|
guardianPrincipalId: text("guardian_principal_id").notNull(),
|
|
178
177
|
hashedDeviceId: text("hashed_device_id").notNull(),
|
|
179
178
|
platform: text("platform").notNull(),
|
|
@@ -190,7 +189,6 @@ export const actorRefreshTokenRecords = sqliteTable(
|
|
|
190
189
|
id: text("id").primaryKey(),
|
|
191
190
|
tokenHash: text("token_hash").notNull(),
|
|
192
191
|
familyId: text("family_id").notNull(),
|
|
193
|
-
assistantId: text("assistant_id").notNull(),
|
|
194
192
|
guardianPrincipalId: text("guardian_principal_id").notNull(),
|
|
195
193
|
hashedDeviceId: text("hashed_device_id").notNull(),
|
|
196
194
|
platform: text("platform").notNull(),
|
|
@@ -11,7 +11,6 @@ import { conversations } from "./conversations.js";
|
|
|
11
11
|
|
|
12
12
|
export const notificationEvents = sqliteTable("notification_events", {
|
|
13
13
|
id: text("id").primaryKey(),
|
|
14
|
-
assistantId: text("assistant_id").notNull(),
|
|
15
14
|
sourceEventName: text("source_event_name").notNull(),
|
|
16
15
|
sourceChannel: text("source_channel").notNull(),
|
|
17
16
|
sourceSessionId: text("source_session_id").notNull(),
|
|
@@ -39,7 +38,6 @@ export const notificationDecisions = sqliteTable("notification_decisions", {
|
|
|
39
38
|
|
|
40
39
|
export const notificationPreferences = sqliteTable("notification_preferences", {
|
|
41
40
|
id: text("id").primaryKey(),
|
|
42
|
-
assistantId: text("assistant_id").notNull(),
|
|
43
41
|
preferenceText: text("preference_text").notNull(),
|
|
44
42
|
appliesWhenJson: text("applies_when_json").notNull().default("{}"),
|
|
45
43
|
priority: integer("priority").notNull().default(0),
|
|
@@ -95,7 +93,6 @@ export const notificationDeliveries = sqliteTable(
|
|
|
95
93
|
notificationDecisionId: text("notification_decision_id")
|
|
96
94
|
.notNull()
|
|
97
95
|
.references(() => notificationDecisions.id, { onDelete: "cascade" }),
|
|
98
|
-
assistantId: text("assistant_id").notNull(),
|
|
99
96
|
channel: text("channel").notNull(),
|
|
100
97
|
destination: text("destination").notNull(),
|
|
101
98
|
status: text("status").notNull().default("pending"),
|
|
@@ -132,7 +129,6 @@ export const conversationAttentionEvents = sqliteTable(
|
|
|
132
129
|
conversationId: text("conversation_id")
|
|
133
130
|
.notNull()
|
|
134
131
|
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
135
|
-
assistantId: text("assistant_id").notNull(),
|
|
136
132
|
sourceChannel: text("source_channel").notNull(),
|
|
137
133
|
signalType: text("signal_type").notNull(),
|
|
138
134
|
confidence: text("confidence").notNull(),
|
|
@@ -147,10 +143,7 @@ export const conversationAttentionEvents = sqliteTable(
|
|
|
147
143
|
table.conversationId,
|
|
148
144
|
table.observedAt,
|
|
149
145
|
),
|
|
150
|
-
index("
|
|
151
|
-
table.assistantId,
|
|
152
|
-
table.observedAt,
|
|
153
|
-
),
|
|
146
|
+
index("idx_conv_attn_events_observed").on(table.observedAt),
|
|
154
147
|
index("idx_conv_attn_events_channel_observed").on(
|
|
155
148
|
table.sourceChannel,
|
|
156
149
|
table.observedAt,
|
|
@@ -164,7 +157,6 @@ export const conversationAssistantAttentionState = sqliteTable(
|
|
|
164
157
|
conversationId: text("conversation_id")
|
|
165
158
|
.primaryKey()
|
|
166
159
|
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
167
|
-
assistantId: text("assistant_id").notNull(),
|
|
168
160
|
latestAssistantMessageId: text("latest_assistant_message_id"),
|
|
169
161
|
latestAssistantMessageAt: integer("latest_assistant_message_at"),
|
|
170
162
|
lastSeenAssistantMessageId: text("last_seen_assistant_message_id"),
|
|
@@ -179,13 +171,7 @@ export const conversationAssistantAttentionState = sqliteTable(
|
|
|
179
171
|
updatedAt: integer("updated_at").notNull(),
|
|
180
172
|
},
|
|
181
173
|
(table) => [
|
|
182
|
-
index("
|
|
183
|
-
|
|
184
|
-
table.latestAssistantMessageAt,
|
|
185
|
-
),
|
|
186
|
-
index("idx_conv_attn_state_assistant_last_seen").on(
|
|
187
|
-
table.assistantId,
|
|
188
|
-
table.lastSeenAssistantMessageAt,
|
|
189
|
-
),
|
|
174
|
+
index("idx_conv_attn_state_latest_msg").on(table.latestAssistantMessageAt),
|
|
175
|
+
index("idx_conv_attn_state_last_seen").on(table.lastSeenAssistantMessageAt),
|
|
190
176
|
],
|
|
191
177
|
);
|