@vellumai/assistant 0.4.13 → 0.4.15
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 +77 -38
- package/README.md +10 -12
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +108 -522
- package/src/__tests__/channel-approval-routes.test.ts +92 -239
- package/src/__tests__/channel-approval.test.ts +100 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -6
- package/src/__tests__/conversation-routes.test.ts +11 -4
- package/src/__tests__/guardian-actions-endpoint.test.ts +26 -19
- package/src/__tests__/mcp-health-check.test.ts +65 -0
- package/src/__tests__/permission-types.test.ts +33 -0
- package/src/__tests__/scan-result-store.test.ts +121 -0
- package/src/__tests__/session-agent-loop.test.ts +120 -0
- package/src/__tests__/session-approval-overrides.test.ts +205 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +38 -0
- package/src/amazon/client.ts +8 -5
- package/src/approvals/guardian-decision-primitive.ts +14 -9
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/call-controller.ts +2 -2
- package/src/calls/twilio-routes.ts +2 -2
- package/src/cli/mcp.ts +3 -3
- package/src/cli.ts +24 -0
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +19 -130
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +8 -6
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +49 -14
- package/src/config/bundled-skills/messaging/TOOLS.json +52 -9
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +35 -11
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +3 -1
- package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +5 -6
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +10 -2
- package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +20 -0
- package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +3 -4
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +76 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +10 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +11 -3
- package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +86 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/skills-catalog/SKILL.md +31 -8
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +79 -24
- package/src/config/bundled-skills/sms-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/telegram-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/approval-generators.ts +6 -3
- package/src/daemon/handlers/config-ingress.ts +2 -6
- package/src/daemon/handlers/guardian-actions.ts +1 -1
- package/src/daemon/handlers/sessions.ts +4 -1
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/skills.ts +32 -0
- package/src/daemon/ipc-contract/messages.ts +3 -1
- package/src/daemon/ipc-handler.ts +24 -0
- package/src/daemon/ipc-validate.ts +1 -1
- package/src/daemon/lifecycle.ts +6 -8
- package/src/daemon/server.ts +8 -3
- package/src/daemon/session-agent-loop.ts +19 -1
- package/src/daemon/session-attachments.ts +2 -1
- package/src/daemon/session-history.ts +2 -2
- package/src/daemon/session-process.ts +5 -9
- package/src/daemon/session-surfaces.ts +17 -1
- package/src/daemon/session-tool-setup.ts +216 -69
- package/src/daemon/session.ts +24 -1
- package/src/events/domain-events.ts +1 -1
- package/src/events/tool-domain-event-publisher.ts +5 -10
- package/src/influencer/client.ts +8 -7
- package/src/messaging/providers/gmail/client.ts +33 -1
- package/src/messaging/providers/gmail/mime-builder.ts +5 -1
- package/src/messaging/providers/sms/adapter.ts +3 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +3 -7
- package/src/messaging/providers/whatsapp/adapter.ts +3 -7
- package/src/notifications/adapters/sms.ts +2 -2
- package/src/notifications/adapters/telegram.ts +2 -2
- package/src/permissions/prompter.ts +2 -0
- package/src/permissions/types.ts +11 -1
- package/src/runtime/approval-conversation-turn.ts +4 -0
- package/src/runtime/auth/__tests__/context.test.ts +130 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +277 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +289 -0
- package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +71 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +239 -0
- package/src/runtime/auth/__tests__/policy.test.ts +29 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +166 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +109 -0
- package/src/runtime/auth/__tests__/subject.test.ts +149 -0
- package/src/runtime/auth/__tests__/token-service.test.ts +263 -0
- package/src/runtime/auth/context.ts +62 -0
- package/src/runtime/{actor-refresh-token-service.ts → auth/credential-service.ts} +112 -79
- package/src/runtime/auth/external-assistant-id.ts +69 -0
- package/src/runtime/auth/index.ts +37 -0
- package/src/runtime/auth/middleware.ts +127 -0
- package/src/runtime/auth/policy.ts +17 -0
- package/src/runtime/auth/route-policy.ts +261 -0
- package/src/runtime/auth/scopes.ts +64 -0
- package/src/runtime/auth/subject.ts +68 -0
- package/src/runtime/auth/token-service.ts +275 -0
- package/src/runtime/auth/types.ts +79 -0
- package/src/runtime/channel-approval-parser.ts +11 -5
- package/src/runtime/channel-approval-types.ts +1 -1
- package/src/runtime/channel-approvals.ts +22 -1
- package/src/runtime/guardian-action-followup-executor.ts +2 -2
- package/src/runtime/guardian-context-resolver.ts +15 -0
- package/src/runtime/guardian-decision-types.ts +23 -6
- package/src/runtime/guardian-outbound-actions.ts +4 -22
- package/src/runtime/guardian-reply-router.ts +5 -3
- package/src/runtime/http-server.ts +210 -182
- package/src/runtime/http-types.ts +11 -1
- package/src/runtime/local-actor-identity.ts +25 -0
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/approval-routes.ts +42 -59
- package/src/runtime/routes/channel-route-shared.ts +9 -41
- package/src/runtime/routes/channel-routes.ts +0 -2
- package/src/runtime/routes/conversation-routes.ts +39 -49
- package/src/runtime/routes/events-routes.ts +15 -22
- package/src/runtime/routes/guardian-action-routes.ts +46 -51
- package/src/runtime/routes/guardian-approval-interception.ts +6 -5
- package/src/runtime/routes/guardian-bootstrap-routes.ts +12 -8
- package/src/runtime/routes/guardian-refresh-routes.ts +2 -2
- package/src/runtime/routes/inbound-message-handler.ts +39 -45
- package/src/runtime/routes/pairing-routes.ts +9 -9
- package/src/runtime/routes/secret-routes.ts +90 -45
- package/src/runtime/routes/surface-action-routes.ts +12 -2
- package/src/runtime/routes/trust-rules-routes.ts +13 -0
- package/src/runtime/routes/twilio-routes.ts +3 -3
- package/src/runtime/session-approval-overrides.ts +86 -0
- package/src/security/keychain-to-encrypted-migration.ts +8 -1
- package/src/skills/frontmatter.ts +44 -1
- package/src/tools/permission-checker.ts +226 -74
- package/src/runtime/actor-token-service.ts +0 -234
- package/src/runtime/middleware/actor-token.ts +0 -265
package/ARCHITECTURE.md
CHANGED
|
@@ -22,59 +22,74 @@ This document owns assistant-runtime architecture details. The repo-level archit
|
|
|
22
22
|
- Voice calls mirror the same prompt contract: `CallController` receives guardian context on setup and refreshes it immediately after successful voice challenge verification, so the first post-verification turn is grounded as `actor_role: guardian`.
|
|
23
23
|
- Voice-specific behavior (DTMF/speech verification flow, relay state machine) remains voice-local; only actor-role resolution is shared.
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Single-Header JWT Auth Model
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
All HTTP API requests use a single `Authorization: Bearer <jwt>` header for authentication. The JWT carries identity, permissions, and policy versioning in a unified token.
|
|
28
28
|
|
|
29
|
-
**
|
|
29
|
+
**Token schema (JWT claims):**
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
| Claim | Type | Description |
|
|
32
|
+
|-------|------|-------------|
|
|
33
|
+
| `iss` | `'vellum-auth'` | Issuer — always `vellum-auth` |
|
|
34
|
+
| `aud` | `'vellum-daemon'` or `'vellum-gateway'` | Audience — which service the token targets |
|
|
35
|
+
| `sub` | string | Subject — encodes principal type and identity (see patterns below) |
|
|
36
|
+
| `scope_profile` | string | Named permission bundle (see profiles below) |
|
|
37
|
+
| `exp` | number | Expiry timestamp (seconds since epoch) |
|
|
38
|
+
| `policy_epoch` | number | Policy version — stale tokens are rejected with `refresh_required` |
|
|
39
|
+
| `iat` | number | Issued-at timestamp |
|
|
40
|
+
| `jti` | string | Unique token ID |
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
**Subject patterns:**
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
| Pattern | Principal Type | Description |
|
|
45
|
+
|---------|---------------|-------------|
|
|
46
|
+
| `actor:<assistantId>:<actorPrincipalId>` | `actor` | Desktop, iOS, or CLI client |
|
|
47
|
+
| `svc:gateway:<assistantId>` | `svc_gateway` | Gateway service (ingress, webhooks) |
|
|
48
|
+
| `ipc:<assistantId>:<sessionId>` | `ipc` | Internal IPC connections |
|
|
49
|
+
| `svc:daemon:self` | n/a | Daemon self-identification (for internal use) |
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
**Scope profiles:**
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
| Profile | Scopes | Used by |
|
|
54
|
+
|---------|--------|---------|
|
|
55
|
+
| `actor_client_v1` | `chat.{read,write}`, `approval.{read,write}`, `settings.{read,write}`, `attachments.{read,write}`, `calls.{read,write}`, `feature_flags.{read,write}` | Desktop, iOS, CLI clients |
|
|
56
|
+
| `gateway_ingress_v1` | `ingress.write`, `internal.write` | Gateway channel inbound + webhook forwarding |
|
|
57
|
+
| `gateway_service_v1` | `settings.read`, `settings.write`, `internal.write` | Gateway service-to-daemon calls |
|
|
58
|
+
| `ipc_v1` | `ipc.all` | Internal IPC connections |
|
|
40
59
|
|
|
41
|
-
**
|
|
60
|
+
**Identity lifecycle:**
|
|
42
61
|
|
|
43
|
-
**
|
|
62
|
+
1. **Bootstrap (loopback-only, macOS/CLI)** — On first launch, the client calls `POST /v1/integrations/guardian/vellum/bootstrap` with `{ platform, deviceId }`. The endpoint is loopback-only and mints a JWT access token + refresh token pair. Returns `{ guardianPrincipalId, accessToken, accessTokenExpiresAt, refreshToken, refreshTokenExpiresAt, refreshAfter, isNew }`.
|
|
44
63
|
|
|
45
|
-
|
|
64
|
+
2. **iOS pairing** — iOS devices obtain JWTs through the QR pairing flow. The pairing response includes `accessToken` and `refreshToken` credentials.
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
- **Dual expiry:** Each refresh token has a 365-day absolute expiry (from issuance) and a 90-day inactivity expiry (from last use). The effective expiry is the earlier of the two. Using a refresh token resets the inactivity window.
|
|
49
|
-
- **Single-use rotation:** Each call to `POST /v1/integrations/guardian/vellum/refresh` consumes the presented refresh token and returns a new actor token + new refresh token pair. The old refresh token is marked as rotated and cannot be reused.
|
|
50
|
-
- **Token family tracking:** Refresh tokens are grouped into families (one family per initial issuance chain). All tokens in a family share a `familyId`.
|
|
51
|
-
- **Replay detection:** If a client presents a refresh token that has already been rotated (i.e., it was used once and a successor was issued), the server treats this as a potential token theft. The entire token family for that device is revoked, forcing re-bootstrap or re-pairing.
|
|
52
|
-
- **Device binding:** Refresh tokens are bound to `(assistantId, guardianPrincipalId, hashedDeviceId)`. A refresh request from a different device binding is rejected.
|
|
53
|
-
- **Hash-only storage:** Only the SHA-256 hex digest of the refresh token is stored in the `actor_refresh_token_records` table. The raw token is returned once and never persisted on the server.
|
|
66
|
+
3. **Refresh** — `POST /v1/integrations/guardian/vellum/refresh` accepts `{ refreshToken }` and returns a new access/refresh token pair. Single-use rotation with replay detection and family-based revocation.
|
|
54
67
|
|
|
55
|
-
|
|
68
|
+
4. **IPC identity** — Local IPC connections use `resolveLocalIpcGuardianContext()` for deterministic identity without tokens.
|
|
56
69
|
|
|
57
|
-
**
|
|
70
|
+
**Route policy enforcement:** Every protected endpoint declares required scopes and allowed principal types in `src/runtime/auth/route-policy.ts`. The `enforcePolicy()` function checks the AuthContext against these requirements and returns 403 when access is denied. A guard test ensures every dispatched endpoint has a corresponding policy entry.
|
|
58
71
|
|
|
59
|
-
**
|
|
72
|
+
**Credential storage:** Only hashed tokens are persisted. Access token hashes go in `credential_records`; refresh token hashes in `refresh_token_records`. Raw tokens are returned once and never stored server-side.
|
|
60
73
|
|
|
61
|
-
**Notification scoping:** Guardian-sensitive notifications
|
|
74
|
+
**Notification scoping:** Guardian-sensitive notifications are annotated with `targetGuardianPrincipalId` for identity-scoped delivery.
|
|
62
75
|
|
|
63
76
|
**Key source files:**
|
|
64
77
|
|
|
65
|
-
| File
|
|
66
|
-
|
|
67
|
-
| `src/runtime/
|
|
68
|
-
| `src/runtime/
|
|
69
|
-
| `src/runtime/
|
|
70
|
-
| `src/runtime/
|
|
71
|
-
| `src/runtime/
|
|
72
|
-
| `src/runtime/
|
|
73
|
-
| `src/runtime/
|
|
74
|
-
| `src/runtime/
|
|
75
|
-
| `src/runtime/routes/guardian-
|
|
76
|
-
| `src/runtime/routes/
|
|
77
|
-
| `src/
|
|
78
|
+
| File | Purpose |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| `src/runtime/auth/types.ts` | Core type definitions: `TokenClaims`, `AuthContext`, `ScopeProfile`, `Scope`, `PrincipalType` |
|
|
81
|
+
| `src/runtime/auth/token-service.ts` | JWT signing, verification, and policy epoch management |
|
|
82
|
+
| `src/runtime/auth/credential-service.ts` | Credential pair minting (access token + refresh token) |
|
|
83
|
+
| `src/runtime/auth/scopes.ts` | Scope profile resolver (`resolveScopeProfile`) |
|
|
84
|
+
| `src/runtime/auth/context.ts` | AuthContext builder from JWT claims |
|
|
85
|
+
| `src/runtime/auth/subject.ts` | Subject string parser (`parseSub`) |
|
|
86
|
+
| `src/runtime/auth/middleware.ts` | JWT bearer auth middleware (`authenticateRequest`) |
|
|
87
|
+
| `src/runtime/auth/route-policy.ts` | Route-level scope/principal enforcement |
|
|
88
|
+
| `src/runtime/routes/guardian-bootstrap-routes.ts` | `POST /v1/integrations/guardian/vellum/bootstrap` (initial JWT issuance) |
|
|
89
|
+
| `src/runtime/routes/guardian-refresh-routes.ts` | `POST /v1/integrations/guardian/vellum/refresh` (token rotation) |
|
|
90
|
+
| `src/runtime/routes/pairing-routes.ts` | JWT credential issuance in pairing flow |
|
|
91
|
+
| `src/runtime/local-actor-identity.ts` | `resolveLocalIpcGuardianContext` — deterministic IPC identity |
|
|
92
|
+
| `src/memory/guardian-bindings.ts` | Guardian binding persistence (shared across all channels) |
|
|
78
93
|
|
|
79
94
|
### Channel-Agnostic Scoped Approval Grants
|
|
80
95
|
|
|
@@ -116,6 +131,30 @@ All guardian approval decisions — regardless of how they arrive — route thro
|
|
|
116
131
|
| `src/daemon/ipc-contract/guardian-actions.ts` | IPC message type definitions for guardian action requests/responses |
|
|
117
132
|
| `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
|
|
118
133
|
|
|
134
|
+
### Temporary Approval Modes (Session-Scoped Overrides)
|
|
135
|
+
|
|
136
|
+
In addition to persistent trust rules (`always_allow` / `always_deny`), the approval system supports two **temporary** approval modes that auto-approve tool confirmations for the duration of a conversation or a fixed time window. These exist to reduce prompt fatigue during intensive sessions without permanently altering the trust configuration.
|
|
137
|
+
|
|
138
|
+
**Two modes:**
|
|
139
|
+
|
|
140
|
+
1. **`allow_thread`** — Auto-approve all tool confirmations for the remainder of the current conversation. The override persists until the session ends, the conversation is closed, or the mode is explicitly cleared.
|
|
141
|
+
2. **`allow_10m`** — Auto-approve all tool confirmations for 10 minutes (configurable). The override expires lazily on the next read after the TTL elapses — no background sweep runs.
|
|
142
|
+
|
|
143
|
+
**Session-scoped, in-memory only:** Overrides are keyed by `conversationId` and stored in an in-memory `Map` inside `session-approval-overrides.ts`. They do not survive daemon restarts, which is intentional — temporary approvals should not outlive the session that created them.
|
|
144
|
+
|
|
145
|
+
**Integration with the permission pipeline:** The permission checker (`src/tools/permission-checker.ts`) checks for an active temporary override via `getEffectiveMode()` before prompting the user. If an active override exists for the current conversation, the confirmation is auto-approved without surfacing a prompt. This check runs after persistent trust rules, so a persistent `deny` rule still takes precedence.
|
|
146
|
+
|
|
147
|
+
**No persistent side effects:** Temporary modes do not write to `trust.json` or create persistent trust rules. They are purely ephemeral. The `buildDecisionActions()` function in `guardian-decision-types.ts` controls whether temporary options (`allow_10m`, `allow_thread`) are surfaced in the approval prompt UI, gated by the `temporaryOptionsAvailable` flag.
|
|
148
|
+
|
|
149
|
+
**Key source files:**
|
|
150
|
+
|
|
151
|
+
| File | Purpose |
|
|
152
|
+
|------|---------|
|
|
153
|
+
| `src/runtime/session-approval-overrides.ts` | In-memory store: `setThreadMode`, `setTimedMode`, `getEffectiveMode`, `clearMode`, `hasActiveOverride`, `clearAll` |
|
|
154
|
+
| `src/permissions/types.ts` | `UserDecision` type (includes `allow_10m`, `allow_thread`, `temporary_override`), `isAllowDecision()` helper |
|
|
155
|
+
| `src/runtime/guardian-decision-types.ts` | `buildDecisionActions()` — controls which temporary options appear in approval prompts |
|
|
156
|
+
| `src/tools/permission-checker.ts` | Permission pipeline integration — checks temporary overrides before prompting |
|
|
157
|
+
|
|
119
158
|
### Canonical Guardian Request System
|
|
120
159
|
|
|
121
160
|
The canonical guardian request system provides a channel-agnostic, unified domain for all guardian approval and question flows. It replaces the fragmented per-channel storage with a single source of truth that works identically for voice calls, Telegram/SMS/WhatsApp, and desktop UI.
|
|
@@ -164,7 +203,7 @@ Guardian verification can be initiated through gateway HTTP endpoints (which for
|
|
|
164
203
|
| `/v1/integrations/guardian/outbound/resend` | POST | Resend the verification code for an active session. Body: `{ channel, assistantId? }` |
|
|
165
204
|
| `/v1/integrations/guardian/outbound/cancel` | POST | Cancel an active outbound verification session. Body: `{ channel, assistantId? }` |
|
|
166
205
|
|
|
167
|
-
All endpoints are
|
|
206
|
+
All endpoints are JWT-authenticated via `Authorization: Bearer <jwt>`. Skills and user-facing tooling should target the gateway URL (default `http://localhost:7830`), not the runtime port.
|
|
168
207
|
|
|
169
208
|
**Shared Business Logic:**
|
|
170
209
|
|
|
@@ -332,7 +371,7 @@ The Slack channel provides text-based messaging via Slack's Socket Mode API. Unl
|
|
|
332
371
|
| `/v1/integrations/slack/channel/config` | POST | Validates and stores credentials. Body: `{ botToken?: string, appToken?: string }` |
|
|
333
372
|
| `/v1/integrations/slack/channel/config` | DELETE | Clears all Slack channel credentials from secure storage and credential metadata |
|
|
334
373
|
|
|
335
|
-
All endpoints are
|
|
374
|
+
All endpoints are JWT-authenticated via `Authorization: Bearer <jwt>`.
|
|
336
375
|
|
|
337
376
|
**Credential storage pattern:**
|
|
338
377
|
|
|
@@ -709,7 +748,7 @@ graph LR
|
|
|
709
748
|
subgraph "~/.vellum/ (Root Files)"
|
|
710
749
|
SOCK["vellum.sock<br/>Unix domain socket"]
|
|
711
750
|
TRUST["protected/trust.json<br/>Tool permission rules"]
|
|
712
|
-
FF_TOKEN["feature-flag-token<br/>
|
|
751
|
+
FF_TOKEN["feature-flag-token<br/>Client token for feature-flag API"]
|
|
713
752
|
end
|
|
714
753
|
|
|
715
754
|
subgraph "~/.vellum/workspace/ (Workspace Files)"
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ cp .env.example .env
|
|
|
47
47
|
| `OLLAMA_API_KEY` | No | — | API key for authenticated Ollama deployments |
|
|
48
48
|
| `OLLAMA_BASE_URL` | No | `http://127.0.0.1:11434/v1` | Ollama base URL |
|
|
49
49
|
| `RUNTIME_HTTP_PORT` | No | — | Enable the HTTP server (required for gateway/web) |
|
|
50
|
-
| `RUNTIME_GATEWAY_ORIGIN_SECRET` | No | — |
|
|
50
|
+
| `RUNTIME_GATEWAY_ORIGIN_SECRET` | No | — | **Deprecated.** Gateway origin is now proven by JWT principal type (`svc_gateway`), not a separate header. |
|
|
51
51
|
| `VELLUM_DAEMON_SOCKET` | No | `~/.vellum/vellum.sock` | Override the daemon socket path |
|
|
52
52
|
|
|
53
53
|
## Update Bulletin
|
|
@@ -199,12 +199,10 @@ Internal forwarding routes (`/v1/internal/twilio/*`) are unaffected — these ac
|
|
|
199
199
|
|
|
200
200
|
### Gateway-Origin Ingress Contract
|
|
201
201
|
|
|
202
|
-
The `/channels/inbound` endpoint requires a
|
|
202
|
+
The `/channels/inbound` endpoint requires a JWT with the `svc_gateway` principal type and `ingress.write` scope to prove the request originated from the gateway. This ensures channel messages can only arrive via the gateway (which performs webhook-level verification) and not via direct HTTP calls that bypass signature checks.
|
|
203
203
|
|
|
204
|
-
- **
|
|
205
|
-
- **
|
|
206
|
-
- **Without any secret:** When neither a dedicated secret nor a bearer token is configured (local dev), gateway-origin validation is skipped entirely.
|
|
207
|
-
- **Auth layer order:** Bearer token authentication (`Authorization` header) is checked first. Gateway-origin validation runs inside the handler.
|
|
204
|
+
- **JWT-based enforcement:** The route policy in `route-policy.ts` restricts `/channels/inbound` to the `svc_gateway` principal type with `ingress.write` scope. Actor and IPC principals are rejected with 403.
|
|
205
|
+
- **Dev bypass:** When `DISABLE_HTTP_AUTH` + `VELLUM_UNSAFE_AUTH_BYPASS=1` are set, JWT verification is skipped and a synthetic dev context is used.
|
|
208
206
|
|
|
209
207
|
## Twilio Setup Primitive
|
|
210
208
|
|
|
@@ -280,12 +278,12 @@ The channel guardian service generates verification challenge instructions with
|
|
|
280
278
|
|
|
281
279
|
### Vellum Guardian Identity (Actor Tokens)
|
|
282
280
|
|
|
283
|
-
The vellum channel (macOS, iOS, CLI) uses
|
|
281
|
+
The vellum channel (macOS, iOS, CLI) uses JWTs to bind guardian identity to HTTP requests. This enables identity-based authentication for the local desktop/mobile channel, paralleling how external channels (Telegram, SMS) use `actorExternalId` for guardian identity.
|
|
284
282
|
|
|
285
|
-
- **Bootstrap**: After hatch, the macOS client calls `POST /v1/integrations/guardian/vellum/bootstrap` with `{ platform, deviceId }`. Returns `{ guardianPrincipalId,
|
|
286
|
-
- **iOS pairing**: The pairing response includes
|
|
287
|
-
- **IPC fallback**: Local IPC (Unix socket) connections resolve identity server-side via `resolveLocalIpcGuardianContext()` without requiring
|
|
288
|
-
- **HTTP enforcement**:
|
|
283
|
+
- **Bootstrap**: After hatch, the macOS client calls `POST /v1/integrations/guardian/vellum/bootstrap` with `{ platform, deviceId }`. Returns `{ guardianPrincipalId, accessToken, accessTokenExpiresAt, refreshToken, refreshTokenExpiresAt, refreshAfter, isNew }`. The endpoint is idempotent -- repeated calls with the same device return the same principal but mint fresh credentials.
|
|
284
|
+
- **iOS pairing**: The pairing response includes `accessToken` and `refreshToken` credentials automatically when a vellum guardian binding exists.
|
|
285
|
+
- **IPC fallback**: Local IPC (Unix socket) connections resolve identity server-side via `resolveLocalIpcGuardianContext()` without requiring a JWT.
|
|
286
|
+
- **HTTP enforcement**: All vellum HTTP routes require a valid JWT via the `Authorization: Bearer <jwt>` header. The JWT carries identity claims (`sub` with principal type and ID) and scope permissions. Route-level enforcement in `route-policy.ts` checks scopes and principal types.
|
|
289
287
|
- **Startup migration**: On daemon start, `ensureVellumGuardianBinding()` backfills a vellum guardian binding for existing installations so the identity system works without requiring a manual bootstrap step.
|
|
290
288
|
|
|
291
289
|
## Guardian Verification and Ingress ACL
|
|
@@ -502,7 +500,7 @@ The image runs as non-root user `assistant` (uid 1001) and exposes port `3001`.
|
|
|
502
500
|
|
|
503
501
|
| Symptom | Cause | Resolution |
|
|
504
502
|
|---------|-------|------------|
|
|
505
|
-
| 403 `
|
|
503
|
+
| 403 `FORBIDDEN` on `/channels/inbound` | JWT does not have `svc_gateway` principal type or `ingress.write` scope | Ensure the gateway is minting JWTs with the `gateway_ingress_v1` scope profile when forwarding channel inbound requests. |
|
|
506
504
|
| Non-guardian actions silently denied | No guardian binding for the channel. The system is fail-closed for unverified channels. | Run the guardian verification flow from the desktop UI to bind a guardian. |
|
|
507
505
|
| Guardian approval expired | The 30-minute TTL elapsed. The proactive sweep auto-denied the approval and notified both parties. | The requester must re-trigger the action. |
|
|
508
506
|
| `forceStrictSideEffects` unexpectedly active | The sender is classified as `non-guardian` or `unverified_channel` | Verify the sender's `actorExternalId` matches the guardian binding, or set up a guardian binding for the channel. |
|