@vellumai/assistant 0.3.27 → 0.4.0
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/ARCHITECTURE.md +81 -4
- package/Dockerfile +2 -2
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +9 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +21 -19
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +1092 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +212 -36
- package/src/__tests__/notification-decision-fallback.test.ts +63 -3
- package/src/__tests__/notification-decision-strategy.test.ts +78 -0
- package/src/__tests__/notification-guardian-path.test.ts +15 -15
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +126 -59
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +358 -24
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +149 -21
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +22 -16
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +33 -6
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +68 -326
- package/src/daemon/session-runtime-assembly.ts +119 -25
- package/src/daemon/session-tool-setup.ts +3 -2
- package/src/daemon/session.ts +4 -3
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +586 -0
- package/src/memory/channel-guardian-store.ts +2 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +56 -0
- package/src/notifications/copy-composer.ts +31 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +173 -0
- package/src/runtime/actor-trust-resolver.ts +221 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -71
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +717 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +20 -2
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +205 -529
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +53 -10
- package/src/tools/types.ts +13 -2
- package/src/util/bundled-asset.ts +31 -0
- package/src/util/canonicalize-identity.ts +52 -0
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store for canonical guardian requests and deliveries.
|
|
3
|
+
*
|
|
4
|
+
* Unifies voice guardian action requests/deliveries and channel guardian
|
|
5
|
+
* approval requests into a single persistence model. Resolution uses
|
|
6
|
+
* compare-and-swap (CAS) semantics: the first writer to transition a
|
|
7
|
+
* request from the expected status wins.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { and, desc, eq } from 'drizzle-orm';
|
|
11
|
+
import { v4 as uuid } from 'uuid';
|
|
12
|
+
|
|
13
|
+
import { getDb, rawChanges } from './db.js';
|
|
14
|
+
import {
|
|
15
|
+
canonicalGuardianDeliveries,
|
|
16
|
+
canonicalGuardianRequests,
|
|
17
|
+
} from './schema.js';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export type CanonicalRequestStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'cancelled';
|
|
24
|
+
|
|
25
|
+
export interface CanonicalGuardianRequest {
|
|
26
|
+
id: string;
|
|
27
|
+
kind: string;
|
|
28
|
+
sourceType: string;
|
|
29
|
+
sourceChannel: string | null;
|
|
30
|
+
conversationId: string | null;
|
|
31
|
+
requesterExternalUserId: string | null;
|
|
32
|
+
requesterChatId: string | null;
|
|
33
|
+
guardianExternalUserId: string | null;
|
|
34
|
+
callSessionId: string | null;
|
|
35
|
+
pendingQuestionId: string | null;
|
|
36
|
+
questionText: string | null;
|
|
37
|
+
requestCode: string | null;
|
|
38
|
+
toolName: string | null;
|
|
39
|
+
inputDigest: string | null;
|
|
40
|
+
status: CanonicalRequestStatus;
|
|
41
|
+
answerText: string | null;
|
|
42
|
+
decidedByExternalUserId: string | null;
|
|
43
|
+
followupState: string | null;
|
|
44
|
+
expiresAt: string | null;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
updatedAt: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CanonicalGuardianDelivery {
|
|
50
|
+
id: string;
|
|
51
|
+
requestId: string;
|
|
52
|
+
destinationChannel: string;
|
|
53
|
+
destinationConversationId: string | null;
|
|
54
|
+
destinationChatId: string | null;
|
|
55
|
+
destinationMessageId: string | null;
|
|
56
|
+
status: string;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Request code generation
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate a short human-readable request code (6 hex chars, uppercase).
|
|
67
|
+
*
|
|
68
|
+
* Checks for collisions against existing PENDING canonical requests and
|
|
69
|
+
* retries up to 5 times to avoid code reuse among active requests.
|
|
70
|
+
*/
|
|
71
|
+
export function generateCanonicalRequestCode(): string {
|
|
72
|
+
const MAX_RETRIES = 5;
|
|
73
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
74
|
+
const code = uuid().replace(/-/g, '').slice(0, 6).toUpperCase();
|
|
75
|
+
// Only check for collisions among pending requests — resolved requests
|
|
76
|
+
// with the same code are harmless since getCanonicalGuardianRequestByCode
|
|
77
|
+
// already filters by status='pending'.
|
|
78
|
+
const existing = getCanonicalGuardianRequestByCodeInternal(code);
|
|
79
|
+
if (!existing) return code;
|
|
80
|
+
}
|
|
81
|
+
// Last resort: return the code even if it collides (extremely unlikely
|
|
82
|
+
// with 16^6 = ~16.7M possible codes).
|
|
83
|
+
return uuid().replace(/-/g, '').slice(0, 6).toUpperCase();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Internal code lookup used by the collision checker. Avoids circular
|
|
88
|
+
* dependency with the public getCanonicalGuardianRequestByCode by
|
|
89
|
+
* inlining the same query logic.
|
|
90
|
+
*/
|
|
91
|
+
function getCanonicalGuardianRequestByCodeInternal(code: string): boolean {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
const row = db
|
|
94
|
+
.select()
|
|
95
|
+
.from(canonicalGuardianRequests)
|
|
96
|
+
.where(
|
|
97
|
+
and(
|
|
98
|
+
eq(canonicalGuardianRequests.requestCode, code),
|
|
99
|
+
eq(canonicalGuardianRequests.status, 'pending'),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
.get();
|
|
103
|
+
return !!row;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Helpers
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
function rowToRequest(row: typeof canonicalGuardianRequests.$inferSelect): CanonicalGuardianRequest {
|
|
111
|
+
return {
|
|
112
|
+
id: row.id,
|
|
113
|
+
kind: row.kind,
|
|
114
|
+
sourceType: row.sourceType,
|
|
115
|
+
sourceChannel: row.sourceChannel,
|
|
116
|
+
conversationId: row.conversationId,
|
|
117
|
+
requesterExternalUserId: row.requesterExternalUserId,
|
|
118
|
+
requesterChatId: row.requesterChatId,
|
|
119
|
+
guardianExternalUserId: row.guardianExternalUserId,
|
|
120
|
+
callSessionId: row.callSessionId,
|
|
121
|
+
pendingQuestionId: row.pendingQuestionId,
|
|
122
|
+
questionText: row.questionText,
|
|
123
|
+
requestCode: row.requestCode,
|
|
124
|
+
toolName: row.toolName,
|
|
125
|
+
inputDigest: row.inputDigest,
|
|
126
|
+
status: row.status as CanonicalRequestStatus,
|
|
127
|
+
answerText: row.answerText,
|
|
128
|
+
decidedByExternalUserId: row.decidedByExternalUserId,
|
|
129
|
+
followupState: row.followupState,
|
|
130
|
+
expiresAt: row.expiresAt,
|
|
131
|
+
createdAt: row.createdAt,
|
|
132
|
+
updatedAt: row.updatedAt,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function rowToDelivery(row: typeof canonicalGuardianDeliveries.$inferSelect): CanonicalGuardianDelivery {
|
|
137
|
+
return {
|
|
138
|
+
id: row.id,
|
|
139
|
+
requestId: row.requestId,
|
|
140
|
+
destinationChannel: row.destinationChannel,
|
|
141
|
+
destinationConversationId: row.destinationConversationId,
|
|
142
|
+
destinationChatId: row.destinationChatId,
|
|
143
|
+
destinationMessageId: row.destinationMessageId,
|
|
144
|
+
status: row.status,
|
|
145
|
+
createdAt: row.createdAt,
|
|
146
|
+
updatedAt: row.updatedAt,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Canonical Guardian Requests
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
export interface CreateCanonicalGuardianRequestParams {
|
|
155
|
+
id?: string;
|
|
156
|
+
kind: string;
|
|
157
|
+
sourceType: string;
|
|
158
|
+
sourceChannel?: string;
|
|
159
|
+
conversationId?: string;
|
|
160
|
+
requesterExternalUserId?: string;
|
|
161
|
+
requesterChatId?: string;
|
|
162
|
+
guardianExternalUserId?: string;
|
|
163
|
+
callSessionId?: string;
|
|
164
|
+
pendingQuestionId?: string;
|
|
165
|
+
questionText?: string;
|
|
166
|
+
requestCode?: string;
|
|
167
|
+
toolName?: string;
|
|
168
|
+
inputDigest?: string;
|
|
169
|
+
status?: CanonicalRequestStatus;
|
|
170
|
+
answerText?: string;
|
|
171
|
+
decidedByExternalUserId?: string;
|
|
172
|
+
followupState?: string;
|
|
173
|
+
expiresAt?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createCanonicalGuardianRequest(params: CreateCanonicalGuardianRequestParams): CanonicalGuardianRequest {
|
|
177
|
+
const db = getDb();
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
const id = params.id ?? uuid();
|
|
180
|
+
|
|
181
|
+
const row = {
|
|
182
|
+
id,
|
|
183
|
+
kind: params.kind,
|
|
184
|
+
sourceType: params.sourceType,
|
|
185
|
+
sourceChannel: params.sourceChannel ?? null,
|
|
186
|
+
conversationId: params.conversationId ?? null,
|
|
187
|
+
requesterExternalUserId: params.requesterExternalUserId ?? null,
|
|
188
|
+
requesterChatId: params.requesterChatId ?? null,
|
|
189
|
+
guardianExternalUserId: params.guardianExternalUserId ?? null,
|
|
190
|
+
callSessionId: params.callSessionId ?? null,
|
|
191
|
+
pendingQuestionId: params.pendingQuestionId ?? null,
|
|
192
|
+
questionText: params.questionText ?? null,
|
|
193
|
+
requestCode: params.requestCode ?? generateCanonicalRequestCode(),
|
|
194
|
+
toolName: params.toolName ?? null,
|
|
195
|
+
inputDigest: params.inputDigest ?? null,
|
|
196
|
+
status: params.status ?? ('pending' as const),
|
|
197
|
+
answerText: params.answerText ?? null,
|
|
198
|
+
decidedByExternalUserId: params.decidedByExternalUserId ?? null,
|
|
199
|
+
followupState: params.followupState ?? null,
|
|
200
|
+
expiresAt: params.expiresAt ?? null,
|
|
201
|
+
createdAt: now,
|
|
202
|
+
updatedAt: now,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
db.insert(canonicalGuardianRequests).values(row).run();
|
|
206
|
+
return rowToRequest(row);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getCanonicalGuardianRequest(id: string): CanonicalGuardianRequest | null {
|
|
210
|
+
const db = getDb();
|
|
211
|
+
const row = db
|
|
212
|
+
.select()
|
|
213
|
+
.from(canonicalGuardianRequests)
|
|
214
|
+
.where(eq(canonicalGuardianRequests.id, id))
|
|
215
|
+
.get();
|
|
216
|
+
return row ? rowToRequest(row) : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Look up a canonical guardian request by its short request code.
|
|
221
|
+
* Scoped to pending (unresolved) requests so that codes recycled by older,
|
|
222
|
+
* already-resolved requests do not collide with the active one.
|
|
223
|
+
*/
|
|
224
|
+
export function getCanonicalGuardianRequestByCode(code: string): CanonicalGuardianRequest | null {
|
|
225
|
+
const db = getDb();
|
|
226
|
+
const row = db
|
|
227
|
+
.select()
|
|
228
|
+
.from(canonicalGuardianRequests)
|
|
229
|
+
.where(
|
|
230
|
+
and(
|
|
231
|
+
eq(canonicalGuardianRequests.requestCode, code),
|
|
232
|
+
eq(canonicalGuardianRequests.status, 'pending'),
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
.get();
|
|
236
|
+
return row ? rowToRequest(row) : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ListCanonicalGuardianRequestsFilters {
|
|
240
|
+
status?: CanonicalRequestStatus;
|
|
241
|
+
guardianExternalUserId?: string;
|
|
242
|
+
requesterExternalUserId?: string;
|
|
243
|
+
conversationId?: string;
|
|
244
|
+
sourceType?: string;
|
|
245
|
+
sourceChannel?: string;
|
|
246
|
+
kind?: string;
|
|
247
|
+
toolName?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function listCanonicalGuardianRequests(filters?: ListCanonicalGuardianRequestsFilters): CanonicalGuardianRequest[] {
|
|
251
|
+
const db = getDb();
|
|
252
|
+
|
|
253
|
+
const conditions = [];
|
|
254
|
+
if (filters?.status) {
|
|
255
|
+
conditions.push(eq(canonicalGuardianRequests.status, filters.status));
|
|
256
|
+
}
|
|
257
|
+
if (filters?.guardianExternalUserId) {
|
|
258
|
+
conditions.push(eq(canonicalGuardianRequests.guardianExternalUserId, filters.guardianExternalUserId));
|
|
259
|
+
}
|
|
260
|
+
if (filters?.conversationId) {
|
|
261
|
+
conditions.push(eq(canonicalGuardianRequests.conversationId, filters.conversationId));
|
|
262
|
+
}
|
|
263
|
+
if (filters?.requesterExternalUserId) {
|
|
264
|
+
conditions.push(eq(canonicalGuardianRequests.requesterExternalUserId, filters.requesterExternalUserId));
|
|
265
|
+
}
|
|
266
|
+
if (filters?.sourceType) {
|
|
267
|
+
conditions.push(eq(canonicalGuardianRequests.sourceType, filters.sourceType));
|
|
268
|
+
}
|
|
269
|
+
if (filters?.sourceChannel) {
|
|
270
|
+
conditions.push(eq(canonicalGuardianRequests.sourceChannel, filters.sourceChannel));
|
|
271
|
+
}
|
|
272
|
+
if (filters?.kind) {
|
|
273
|
+
conditions.push(eq(canonicalGuardianRequests.kind, filters.kind));
|
|
274
|
+
}
|
|
275
|
+
if (filters?.toolName) {
|
|
276
|
+
conditions.push(eq(canonicalGuardianRequests.toolName, filters.toolName));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (conditions.length === 0) {
|
|
280
|
+
return db.select().from(canonicalGuardianRequests).all().map(rowToRequest);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return db
|
|
284
|
+
.select()
|
|
285
|
+
.from(canonicalGuardianRequests)
|
|
286
|
+
.where(and(...conditions))
|
|
287
|
+
.all()
|
|
288
|
+
.map(rowToRequest);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface UpdateCanonicalGuardianRequestParams {
|
|
292
|
+
status?: CanonicalRequestStatus;
|
|
293
|
+
answerText?: string;
|
|
294
|
+
decidedByExternalUserId?: string;
|
|
295
|
+
followupState?: string;
|
|
296
|
+
expiresAt?: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function updateCanonicalGuardianRequest(
|
|
300
|
+
id: string,
|
|
301
|
+
updates: UpdateCanonicalGuardianRequestParams,
|
|
302
|
+
): CanonicalGuardianRequest | null {
|
|
303
|
+
const db = getDb();
|
|
304
|
+
const now = new Date().toISOString();
|
|
305
|
+
|
|
306
|
+
const setValues: Record<string, unknown> = { updatedAt: now };
|
|
307
|
+
if (updates.status !== undefined) setValues.status = updates.status;
|
|
308
|
+
if (updates.answerText !== undefined) setValues.answerText = updates.answerText;
|
|
309
|
+
if (updates.decidedByExternalUserId !== undefined) setValues.decidedByExternalUserId = updates.decidedByExternalUserId;
|
|
310
|
+
if (updates.followupState !== undefined) setValues.followupState = updates.followupState;
|
|
311
|
+
if (updates.expiresAt !== undefined) setValues.expiresAt = updates.expiresAt;
|
|
312
|
+
|
|
313
|
+
db.update(canonicalGuardianRequests)
|
|
314
|
+
.set(setValues)
|
|
315
|
+
.where(eq(canonicalGuardianRequests.id, id))
|
|
316
|
+
.run();
|
|
317
|
+
|
|
318
|
+
return getCanonicalGuardianRequest(id);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface ResolveDecision {
|
|
322
|
+
status: CanonicalRequestStatus;
|
|
323
|
+
answerText?: string;
|
|
324
|
+
decidedByExternalUserId?: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Compare-and-swap resolve: only transitions the request from `expectedStatus`
|
|
329
|
+
* to the new status atomically. Returns the updated request on success, or
|
|
330
|
+
* null if the current status did not match `expectedStatus` (first-writer-wins).
|
|
331
|
+
*/
|
|
332
|
+
export function resolveCanonicalGuardianRequest(
|
|
333
|
+
id: string,
|
|
334
|
+
expectedStatus: CanonicalRequestStatus,
|
|
335
|
+
decision: ResolveDecision,
|
|
336
|
+
): CanonicalGuardianRequest | null {
|
|
337
|
+
const db = getDb();
|
|
338
|
+
const now = new Date().toISOString();
|
|
339
|
+
|
|
340
|
+
const setValues: Record<string, unknown> = {
|
|
341
|
+
status: decision.status,
|
|
342
|
+
updatedAt: now,
|
|
343
|
+
};
|
|
344
|
+
if (decision.answerText !== undefined) setValues.answerText = decision.answerText;
|
|
345
|
+
if (decision.decidedByExternalUserId !== undefined) setValues.decidedByExternalUserId = decision.decidedByExternalUserId;
|
|
346
|
+
|
|
347
|
+
db.update(canonicalGuardianRequests)
|
|
348
|
+
.set(setValues)
|
|
349
|
+
.where(
|
|
350
|
+
and(
|
|
351
|
+
eq(canonicalGuardianRequests.id, id),
|
|
352
|
+
eq(canonicalGuardianRequests.status, expectedStatus),
|
|
353
|
+
),
|
|
354
|
+
)
|
|
355
|
+
.run();
|
|
356
|
+
|
|
357
|
+
if (rawChanges() === 0) return null;
|
|
358
|
+
|
|
359
|
+
return getCanonicalGuardianRequest(id);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Canonical Guardian Deliveries
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
export interface CreateCanonicalGuardianDeliveryParams {
|
|
367
|
+
id?: string;
|
|
368
|
+
requestId: string;
|
|
369
|
+
destinationChannel: string;
|
|
370
|
+
destinationConversationId?: string;
|
|
371
|
+
destinationChatId?: string;
|
|
372
|
+
destinationMessageId?: string;
|
|
373
|
+
status?: string;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function createCanonicalGuardianDelivery(params: CreateCanonicalGuardianDeliveryParams): CanonicalGuardianDelivery {
|
|
377
|
+
const db = getDb();
|
|
378
|
+
const now = new Date().toISOString();
|
|
379
|
+
const id = params.id ?? uuid();
|
|
380
|
+
|
|
381
|
+
const row = {
|
|
382
|
+
id,
|
|
383
|
+
requestId: params.requestId,
|
|
384
|
+
destinationChannel: params.destinationChannel,
|
|
385
|
+
destinationConversationId: params.destinationConversationId ?? null,
|
|
386
|
+
destinationChatId: params.destinationChatId ?? null,
|
|
387
|
+
destinationMessageId: params.destinationMessageId ?? null,
|
|
388
|
+
status: params.status ?? ('pending' as const),
|
|
389
|
+
createdAt: now,
|
|
390
|
+
updatedAt: now,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
db.insert(canonicalGuardianDeliveries).values(row).run();
|
|
394
|
+
return rowToDelivery(row);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function listCanonicalGuardianDeliveries(requestId: string): CanonicalGuardianDelivery[] {
|
|
398
|
+
const db = getDb();
|
|
399
|
+
return db
|
|
400
|
+
.select()
|
|
401
|
+
.from(canonicalGuardianDeliveries)
|
|
402
|
+
.where(eq(canonicalGuardianDeliveries.requestId, requestId))
|
|
403
|
+
.all()
|
|
404
|
+
.map(rowToDelivery);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* List pending canonical requests that were delivered to a specific
|
|
409
|
+
* destination conversation.
|
|
410
|
+
*
|
|
411
|
+
* This bridges inbound guardian replies (which arrive on the destination
|
|
412
|
+
* conversation) back to their canonical request records. The caller can
|
|
413
|
+
* optionally scope by destination channel when the same conversation ID
|
|
414
|
+
* namespace could exist across channels.
|
|
415
|
+
*/
|
|
416
|
+
export function listPendingCanonicalGuardianRequestsByDestinationConversation(
|
|
417
|
+
destinationConversationId: string,
|
|
418
|
+
destinationChannel?: string,
|
|
419
|
+
): CanonicalGuardianRequest[] {
|
|
420
|
+
const db = getDb();
|
|
421
|
+
|
|
422
|
+
const deliveryConditions = [eq(canonicalGuardianDeliveries.destinationConversationId, destinationConversationId)];
|
|
423
|
+
if (destinationChannel) {
|
|
424
|
+
deliveryConditions.push(eq(canonicalGuardianDeliveries.destinationChannel, destinationChannel));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const deliveries = db
|
|
428
|
+
.select()
|
|
429
|
+
.from(canonicalGuardianDeliveries)
|
|
430
|
+
.where(and(...deliveryConditions))
|
|
431
|
+
.all();
|
|
432
|
+
|
|
433
|
+
if (deliveries.length === 0) return [];
|
|
434
|
+
|
|
435
|
+
const seenRequestIds = new Set<string>();
|
|
436
|
+
const pendingRequests: CanonicalGuardianRequest[] = [];
|
|
437
|
+
|
|
438
|
+
for (const delivery of deliveries) {
|
|
439
|
+
if (seenRequestIds.has(delivery.requestId)) continue;
|
|
440
|
+
seenRequestIds.add(delivery.requestId);
|
|
441
|
+
|
|
442
|
+
const request = getCanonicalGuardianRequest(delivery.requestId);
|
|
443
|
+
if (request && request.status === 'pending') {
|
|
444
|
+
pendingRequests.push(request);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return pendingRequests;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* List pending canonical requests that were delivered to a specific
|
|
453
|
+
* destination chat (channel + chatId pair).
|
|
454
|
+
*
|
|
455
|
+
* This bridges inbound guardian replies (which arrive on a specific chat)
|
|
456
|
+
* back to their canonical request records. Unlike the conversation-based
|
|
457
|
+
* variant, this uses the chat-level addressing that channel transports
|
|
458
|
+
* (Telegram, SMS) natively provide — critical for voice-originated
|
|
459
|
+
* `pending_question` requests that lack `guardianExternalUserId`.
|
|
460
|
+
*/
|
|
461
|
+
export function listPendingCanonicalGuardianRequestsByDestinationChat(
|
|
462
|
+
destinationChannel: string,
|
|
463
|
+
destinationChatId: string,
|
|
464
|
+
): CanonicalGuardianRequest[] {
|
|
465
|
+
const db = getDb();
|
|
466
|
+
|
|
467
|
+
const deliveries = db
|
|
468
|
+
.select()
|
|
469
|
+
.from(canonicalGuardianDeliveries)
|
|
470
|
+
.where(
|
|
471
|
+
and(
|
|
472
|
+
eq(canonicalGuardianDeliveries.destinationChannel, destinationChannel),
|
|
473
|
+
eq(canonicalGuardianDeliveries.destinationChatId, destinationChatId),
|
|
474
|
+
),
|
|
475
|
+
)
|
|
476
|
+
.all();
|
|
477
|
+
|
|
478
|
+
if (deliveries.length === 0) return [];
|
|
479
|
+
|
|
480
|
+
const seenRequestIds = new Set<string>();
|
|
481
|
+
const pendingRequests: CanonicalGuardianRequest[] = [];
|
|
482
|
+
|
|
483
|
+
for (const delivery of deliveries) {
|
|
484
|
+
if (seenRequestIds.has(delivery.requestId)) continue;
|
|
485
|
+
seenRequestIds.add(delivery.requestId);
|
|
486
|
+
|
|
487
|
+
const request = getCanonicalGuardianRequest(delivery.requestId);
|
|
488
|
+
if (request && request.status === 'pending') {
|
|
489
|
+
pendingRequests.push(request);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return pendingRequests;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export interface UpdateCanonicalGuardianDeliveryParams {
|
|
497
|
+
status?: string;
|
|
498
|
+
destinationMessageId?: string;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
// Call-controller convenience functions
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Find the most recent pending canonical guardian request for a given call session.
|
|
507
|
+
* Used by the call-controller's consultation timeout handler.
|
|
508
|
+
*/
|
|
509
|
+
export function getPendingCanonicalRequestByCallSessionId(callSessionId: string): CanonicalGuardianRequest | null {
|
|
510
|
+
const db = getDb();
|
|
511
|
+
const row = db
|
|
512
|
+
.select()
|
|
513
|
+
.from(canonicalGuardianRequests)
|
|
514
|
+
.where(
|
|
515
|
+
and(
|
|
516
|
+
eq(canonicalGuardianRequests.callSessionId, callSessionId),
|
|
517
|
+
eq(canonicalGuardianRequests.status, 'pending'),
|
|
518
|
+
),
|
|
519
|
+
)
|
|
520
|
+
.orderBy(desc(canonicalGuardianRequests.createdAt))
|
|
521
|
+
.get();
|
|
522
|
+
return row ? rowToRequest(row) : null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Find a canonical guardian request by its linked pending question ID.
|
|
527
|
+
* Used after async dispatch completes to locate the newly created request.
|
|
528
|
+
*/
|
|
529
|
+
export function getCanonicalRequestByPendingQuestionId(questionId: string): CanonicalGuardianRequest | null {
|
|
530
|
+
const db = getDb();
|
|
531
|
+
const row = db
|
|
532
|
+
.select()
|
|
533
|
+
.from(canonicalGuardianRequests)
|
|
534
|
+
.where(eq(canonicalGuardianRequests.pendingQuestionId, questionId))
|
|
535
|
+
.get();
|
|
536
|
+
return row ? rowToRequest(row) : null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Expire a canonical guardian request and all its deliveries.
|
|
541
|
+
* Atomically transitions the request from 'pending' to 'expired'.
|
|
542
|
+
*/
|
|
543
|
+
export function expireCanonicalGuardianRequest(id: string): void {
|
|
544
|
+
const db = getDb();
|
|
545
|
+
const now = new Date().toISOString();
|
|
546
|
+
|
|
547
|
+
db.update(canonicalGuardianRequests)
|
|
548
|
+
.set({ status: 'expired', updatedAt: now })
|
|
549
|
+
.where(
|
|
550
|
+
and(
|
|
551
|
+
eq(canonicalGuardianRequests.id, id),
|
|
552
|
+
eq(canonicalGuardianRequests.status, 'pending'),
|
|
553
|
+
),
|
|
554
|
+
)
|
|
555
|
+
.run();
|
|
556
|
+
|
|
557
|
+
db.update(canonicalGuardianDeliveries)
|
|
558
|
+
.set({ status: 'expired', updatedAt: now })
|
|
559
|
+
.where(eq(canonicalGuardianDeliveries.requestId, id))
|
|
560
|
+
.run();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function updateCanonicalGuardianDelivery(
|
|
564
|
+
id: string,
|
|
565
|
+
updates: UpdateCanonicalGuardianDeliveryParams,
|
|
566
|
+
): CanonicalGuardianDelivery | null {
|
|
567
|
+
const db = getDb();
|
|
568
|
+
const now = new Date().toISOString();
|
|
569
|
+
|
|
570
|
+
const setValues: Record<string, unknown> = { updatedAt: now };
|
|
571
|
+
if (updates.status !== undefined) setValues.status = updates.status;
|
|
572
|
+
if (updates.destinationMessageId !== undefined) setValues.destinationMessageId = updates.destinationMessageId;
|
|
573
|
+
|
|
574
|
+
db.update(canonicalGuardianDeliveries)
|
|
575
|
+
.set(setValues)
|
|
576
|
+
.where(eq(canonicalGuardianDeliveries.id, id))
|
|
577
|
+
.run();
|
|
578
|
+
|
|
579
|
+
const row = db
|
|
580
|
+
.select()
|
|
581
|
+
.from(canonicalGuardianDeliveries)
|
|
582
|
+
.where(eq(canonicalGuardianDeliveries.id, id))
|
|
583
|
+
.get();
|
|
584
|
+
|
|
585
|
+
return row ? rowToDelivery(row) : null;
|
|
586
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
export {
|
|
14
14
|
type ApprovalRequestStatus,
|
|
15
15
|
countPendingByConversation,
|
|
16
|
+
// @internal — test-only helpers; production code uses canonical-guardian-store
|
|
16
17
|
createApprovalRequest,
|
|
17
18
|
findPendingAccessRequestForRequester,
|
|
18
19
|
getAllPendingApprovalsByGuardianChat,
|
|
@@ -36,6 +37,7 @@ export {
|
|
|
36
37
|
createBinding,
|
|
37
38
|
getActiveBinding,
|
|
38
39
|
type GuardianBinding,
|
|
40
|
+
listActiveBindingsByAssistant,
|
|
39
41
|
revokeBinding,
|
|
40
42
|
} from './guardian-bindings.js';
|
|
41
43
|
export {
|
|
@@ -39,7 +39,7 @@ export const messageMetadataSchema = z.object({
|
|
|
39
39
|
assistantMessageInterface: interfaceIdSchema.optional(),
|
|
40
40
|
subagentNotification: subagentNotificationSchema.optional(),
|
|
41
41
|
// Provenance fields for trust-aware memory gating (M3)
|
|
42
|
-
|
|
42
|
+
provenanceTrustClass: z.enum(['guardian', 'trusted_contact', 'unknown']).optional(),
|
|
43
43
|
provenanceSourceChannel: channelIdSchema.optional(),
|
|
44
44
|
provenanceGuardianExternalUserId: z.string().optional(),
|
|
45
45
|
provenanceRequesterIdentifier: z.string().optional(),
|
|
@@ -49,14 +49,14 @@ export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Extract provenance metadata fields from a GuardianRuntimeContext.
|
|
52
|
-
* When no guardian context is provided, defaults to '
|
|
53
|
-
*
|
|
52
|
+
* When no guardian context is provided, defaults to 'unknown' because the
|
|
53
|
+
* absence of trust context means we cannot verify trust —
|
|
54
54
|
* callers with actual guardian trust should always supply a real context.
|
|
55
55
|
*/
|
|
56
56
|
export function provenanceFromGuardianContext(ctx: GuardianRuntimeContext | null | undefined): Record<string, unknown> {
|
|
57
|
-
if (!ctx) return {
|
|
57
|
+
if (!ctx) return { provenanceTrustClass: 'unknown' };
|
|
58
58
|
return {
|
|
59
|
-
|
|
59
|
+
provenanceTrustClass: ctx.trustClass,
|
|
60
60
|
provenanceSourceChannel: ctx.sourceChannel,
|
|
61
61
|
provenanceGuardianExternalUserId: ctx.guardianExternalUserId,
|
|
62
62
|
provenanceRequesterIdentifier: ctx.requesterIdentifier,
|
|
@@ -280,7 +280,7 @@ export async function addMessage(conversationId: string, role: string, content:
|
|
|
280
280
|
const config = getConfig();
|
|
281
281
|
const scopeId = getConversationMemoryScopeId(conversationId);
|
|
282
282
|
const parsed = metadata ? messageMetadataSchema.safeParse(metadata) : null;
|
|
283
|
-
const
|
|
283
|
+
const provenanceTrustClass = parsed?.success ? parsed.data.provenanceTrustClass : undefined;
|
|
284
284
|
indexMessageNow({
|
|
285
285
|
messageId: message.id,
|
|
286
286
|
conversationId: message.conversationId,
|
|
@@ -288,7 +288,7 @@ export async function addMessage(conversationId: string, role: string, content:
|
|
|
288
288
|
content: message.content,
|
|
289
289
|
createdAt: message.createdAt,
|
|
290
290
|
scopeId,
|
|
291
|
-
|
|
291
|
+
provenanceTrustClass,
|
|
292
292
|
}, config.memory);
|
|
293
293
|
} catch (err) {
|
|
294
294
|
log.warn({ err, conversationId, messageId: message.id }, 'Failed to index message for memory');
|
package/src/memory/db-init.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
addCoreColumns,
|
|
4
4
|
createAssistantInboxTables,
|
|
5
5
|
createCallSessionsTables,
|
|
6
|
+
createCanonicalGuardianTables,
|
|
6
7
|
createChannelGuardianTables,
|
|
7
8
|
createContactsAndTriageTables,
|
|
8
9
|
createConversationAttentionTables,
|
|
@@ -18,6 +19,8 @@ import {
|
|
|
18
19
|
createTasksAndWorkItemsTables,
|
|
19
20
|
createWatchersAndLogsTables,
|
|
20
21
|
migrateCallSessionMode,
|
|
22
|
+
migrateCanonicalGuardianDeliveriesDestinationIndex,
|
|
23
|
+
migrateCanonicalGuardianRequesterChatId,
|
|
21
24
|
migrateChannelInboundDeliveredSegments,
|
|
22
25
|
migrateConversationsThreadTypeIndex,
|
|
23
26
|
migrateFkCascadeRebuilds,
|
|
@@ -29,9 +32,11 @@ import {
|
|
|
29
32
|
migrateGuardianVerificationPurpose,
|
|
30
33
|
migrateGuardianVerificationSessions,
|
|
31
34
|
migrateMessagesFtsBackfill,
|
|
35
|
+
migrateNormalizePhoneIdentities,
|
|
32
36
|
migrateNotificationDeliveryThreadDecision,
|
|
33
37
|
migrateReminderRoutingIntent,
|
|
34
38
|
migrateSchemaIndexesAndColumns,
|
|
39
|
+
migrateVoiceInviteColumns,
|
|
35
40
|
recoverCrashedMigrations,
|
|
36
41
|
runComplexMigrations,
|
|
37
42
|
runLateMigrations,
|
|
@@ -145,5 +150,20 @@ export function initializeDb(): void {
|
|
|
145
150
|
// 23. Thread decision audit columns on notification_deliveries
|
|
146
151
|
migrateNotificationDeliveryThreadDecision(database);
|
|
147
152
|
|
|
153
|
+
// 24. Canonical guardian requests and deliveries (unified cross-source guardian domain)
|
|
154
|
+
createCanonicalGuardianTables(database);
|
|
155
|
+
|
|
156
|
+
// 24b. Add requester_chat_id to canonical_guardian_requests (chat ID != user ID on some channels)
|
|
157
|
+
migrateCanonicalGuardianRequesterChatId(database);
|
|
158
|
+
|
|
159
|
+
// 24c. Composite index on canonical_guardian_deliveries(destination_channel, destination_chat_id) for chat-based lookups
|
|
160
|
+
migrateCanonicalGuardianDeliveriesDestinationIndex(database);
|
|
161
|
+
|
|
162
|
+
// 25. Normalize phone-like identity fields to E.164 across guardian and ingress tables
|
|
163
|
+
migrateNormalizePhoneIdentities(database);
|
|
164
|
+
|
|
165
|
+
// 26. Voice invite columns on assistant_ingress_invites
|
|
166
|
+
migrateVoiceInviteColumns(database);
|
|
167
|
+
|
|
148
168
|
validateMigrationState(database);
|
|
149
169
|
}
|