@mycadet/gateway 0.12.7 → 0.12.9
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/README.md +3 -3
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/ces-client/package.json +1 -1
- package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
- package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
- package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/openapi.json +17 -17
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +1 -1
- package/src/__tests__/actor-token-revocation.test.ts +0 -1
- package/src/__tests__/{backup-worker-platform-mode.test.ts → backup-worker-cloud-mode.test.ts} +4 -4
- package/src/__tests__/channel-verification-session-proxy.test.ts +0 -1
- package/src/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +72 -86
- package/src/__tests__/config.test.ts +0 -24
- package/src/__tests__/contacts-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/credential-requests.test.ts +6 -6
- package/src/__tests__/{credential-watcher-managed-bootstrap.test.ts → credential-watcher-cloud-bootstrap.test.ts} +1 -1
- package/src/__tests__/edge-auth.test.ts +18 -18
- package/src/__tests__/edge-guardian-auth.test.ts +10 -10
- package/src/__tests__/guardian-init-lockfile.test.ts +6 -8
- package/src/__tests__/guardian-pin.test.ts +8 -8
- package/src/__tests__/handle-inbound-admission.test.ts +0 -1
- package/src/__tests__/handle-inbound-app-context.test.ts +0 -1
- package/src/__tests__/handle-inbound-invite-intercept.test.ts +0 -1
- package/src/__tests__/handle-inbound-trust-verdict.test.ts +0 -1
- package/src/__tests__/inbound-contact-seed.test.ts +1 -1
- package/src/__tests__/live-voice-websocket.test.ts +12 -12
- package/src/__tests__/load-guards.test.ts +0 -1
- package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -1
- package/src/__tests__/oauth-callback.test.ts +0 -1
- package/src/__tests__/remote-feature-flag-sync.test.ts +35 -45
- package/src/__tests__/resolve-assistant.test.ts +0 -1
- package/src/__tests__/route-schema-guard.test.ts +4 -1
- package/src/__tests__/runtime-client.test.ts +1 -2
- package/src/__tests__/runtime-health-proxy.test.ts +0 -1
- package/src/__tests__/runtime-proxy-auth.test.ts +3 -2
- package/src/__tests__/runtime-proxy-device-stamp.test.ts +0 -1
- package/src/__tests__/runtime-proxy-error-body.test.ts +0 -1
- package/src/__tests__/runtime-proxy.test.ts +0 -1
- package/src/__tests__/runtime-stream-test-utils.ts +0 -1
- package/src/__tests__/shared-invite-registration.test.ts +15 -32
- package/src/__tests__/slack-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/slack-display-name.test.ts +0 -1
- package/src/__tests__/slack-normalize.test.ts +0 -1
- package/src/__tests__/slack-reaction-normalize.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-catchup.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-group-dm.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-liveness.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-outbound-root.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-thread-tracking.test.ts +0 -1
- package/src/__tests__/speech-relay-websocket.test.ts +16 -14
- package/src/__tests__/stt-stream-websocket.test.ts +5 -5
- package/src/__tests__/telegram-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/telegram-webhook-drop-visibility.test.ts +0 -1
- package/src/__tests__/telegram-webhook-handler.test.ts +0 -1
- package/src/__tests__/telegram-webhook-manager.test.ts +79 -80
- package/src/__tests__/twilio-media-websocket.test.ts +0 -1
- package/src/__tests__/twilio-webhooks.test.ts +6 -7
- package/src/__tests__/whatsapp-download.test.ts +0 -1
- package/src/__tests__/whatsapp-webhook.test.ts +0 -1
- package/src/attachments/ingest.test.ts +0 -1
- package/src/auth/guardian-bootstrap.ts +16 -15
- package/src/auth/scopes.ts +1 -1
- package/src/auth-fallback-reporter.ts +1 -1
- package/src/backup/backup-worker.ts +2 -2
- package/src/channels/inbound-event.ts +3 -3
- package/src/channels/ingress-inbound-vendors.test.ts +2 -2
- package/src/channels/ingress-inbound.ts +1 -1
- package/src/channels/ingress-verification.test.ts +2 -2
- package/src/channels/ingress-verification.ts +4 -4
- package/src/channels/plugin-ingress-approvals.test.ts +1 -1
- package/src/channels/plugin-ingress-approvals.ts +1 -1
- package/src/channels/plugin-ingress.test.ts +3 -3
- package/src/channels/plugin-ingress.ts +3 -3
- package/src/channels/room-admission.ts +2 -2
- package/src/cli/contacts.ts +3 -1
- package/src/cli/index.ts +1 -1
- package/src/{platform-identity.ts → cloud-identity.ts} +60 -68
- package/src/cloud-url.ts +25 -0
- package/src/{platform-user-id.ts → cloud-user-id.ts} +4 -4
- package/src/config.ts +2 -4
- package/src/credential-reader.ts +2 -2
- package/src/db/assistant-db-proxy.ts +1 -1
- package/src/discord/forward.ts +1 -1
- package/src/email/normalize.ts +4 -4
- package/src/email/register-callback.test.ts +27 -34
- package/src/email/register-callback.ts +21 -26
- package/src/feature-flag-registry.json +5 -5
- package/src/feature-flag-remote-store.ts +2 -2
- package/src/feature-flag-resolver.ts +5 -5
- package/src/http/cadet-signature.ts +1 -1
- package/src/http/edge-forwarded-header.ts +1 -1
- package/src/http/loopback-guard.ts +1 -1
- package/src/http/middleware/auth.ts +22 -22
- package/src/http/router.ts +1 -1
- package/src/http/routes/auto-approve-thresholds-routes.ts +1 -1
- package/src/http/routes/cadet-identity.ts +2 -2
- package/src/http/routes/channel-admission-policy-routes.ts +2 -2
- package/src/http/routes/channel-ingress-routes.ts +3 -3
- package/src/http/routes/channel-ingress.test.ts +13 -2
- package/src/http/routes/channel-permission-overrides-routes.ts +3 -3
- package/src/http/routes/channel-verification-session-proxy.ts +8 -22
- package/src/http/routes/{platform-push-proxy.test.ts → cloud-push-proxy.test.ts} +21 -22
- package/src/http/routes/{platform-push-proxy.ts → cloud-push-proxy.ts} +13 -14
- package/src/http/routes/contacts-control-plane-proxy.ts +3 -3
- package/src/http/routes/contacts-control-plane-routes.ts +5 -5
- package/src/http/routes/email-webhook.test.ts +2 -3
- package/src/http/routes/email-webhook.ts +4 -4
- package/src/http/routes/feature-flags.ts +1 -1
- package/src/http/routes/guardian-channel-create.ts +3 -3
- package/src/http/routes/guardian-pin.ts +9 -9
- package/src/http/routes/inbound-register.ts +2 -2
- package/src/http/routes/ingress-domains.test.ts +129 -0
- package/src/http/routes/ingress-domains.ts +73 -0
- package/src/http/routes/live-voice-websocket.ts +2 -2
- package/src/http/routes/log-export.test.ts +0 -1
- package/src/http/routes/log-tail.test.ts +0 -1
- package/src/http/routes/migration-proxy.ts +1 -1
- package/src/http/routes/plugin-webhook-websocket.test.ts +3 -3
- package/src/http/routes/plugin-webhook.test.ts +9 -8
- package/src/http/routes/plugin-webhook.ts +1 -1
- package/src/http/routes/speech-relay-websocket.ts +8 -11
- package/src/http/routes/stt-stream-websocket.ts +1 -1
- package/src/http/routes/telegram-webhook.test.ts +0 -1
- package/src/http/routes/trust-rules-routes.ts +2 -2
- package/src/http/routes/twilio-voice-verify-callback.ts +3 -3
- package/src/http/routes/twilio-voice-webhook.test.ts +3 -4
- package/src/http/routes/twilio-voice-webhook.ts +3 -3
- package/src/http/routes/whatsapp-webhook.test.ts +0 -1
- package/src/index.ts +57 -56
- package/src/ipc/credential-request-handlers.ts +4 -4
- package/src/ipc/debug-export-handlers.test.ts +0 -1
- package/src/ipc/log-tail-handlers.test.ts +0 -1
- package/src/ipc/risk-classification-handlers.test.ts +4 -4
- package/src/ipc/risk-classification-handlers.ts +2 -2
- package/src/remote-feature-flag-sync.ts +17 -19
- package/src/risk/command-registry/commands/assistant.ts +2 -2
- package/src/risk/skill-risk-classifier.test.ts +24 -24
- package/src/risk/skill-risk-classifier.ts +10 -10
- package/src/runtime/client.ts +17 -17
- package/src/schema.ts +29 -29
- package/src/telegram/send.test.ts +0 -1
- package/src/telegram/webhook-manager.ts +33 -38
- package/src/tunnel/allowed-paths.test.ts +3 -3
- package/src/tunnel/allowed-paths.ts +1 -1
- package/src/tunnel/client.test.ts +20 -21
- package/src/tunnel/client.ts +16 -17
- package/src/verification/shared-invite-registration.ts +22 -17
- package/src/version.ts +1 -1
- package/src/platform-url.ts +0 -24
|
@@ -25,7 +25,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
25
25
|
import { mintServiceToken } from "../auth/token-exchange.js";
|
|
26
26
|
import { readConfigFileOrEmpty } from "../config-file-utils.js";
|
|
27
27
|
import { fetchImpl } from "../fetch.js";
|
|
28
|
-
import {
|
|
28
|
+
import { isCloudServedBuild } from "../feature-flag-resolver.js";
|
|
29
29
|
import { getLogger } from "../logger.js";
|
|
30
30
|
import { getGatewaySecurityDir } from "../paths.js";
|
|
31
31
|
import { ensureBackupKey } from "./backup-key.js";
|
|
@@ -398,7 +398,7 @@ function createDisabledBackupWorkerHandle(): BackupWorkerHandle {
|
|
|
398
398
|
* runOnce() methods.
|
|
399
399
|
*/
|
|
400
400
|
export function startBackupWorker(deps: BackupDeps): BackupWorkerHandle {
|
|
401
|
-
if (
|
|
401
|
+
if (isCloudServedBuild()) {
|
|
402
402
|
log.info("Backup worker disabled in platform mode");
|
|
403
403
|
return createDisabledBackupWorkerHandle();
|
|
404
404
|
}
|
|
@@ -114,7 +114,7 @@ interface InboundEventBase<C extends InboundChannelId> {
|
|
|
114
114
|
isDirectMessage?: boolean;
|
|
115
115
|
/**
|
|
116
116
|
* Whether the message addresses the assistant by name, an @-mention on
|
|
117
|
-
* the
|
|
117
|
+
* the clouds that have one. Stated only where the channel proved it,
|
|
118
118
|
* in whichever direction it can prove; absent means "not established".
|
|
119
119
|
* Slack states it for an `app_mention`; Discord reads its own id out of
|
|
120
120
|
* the mentions array. The runtime reads it to decide whether a reply is
|
|
@@ -122,11 +122,11 @@ interface InboundEventBase<C extends InboundChannelId> {
|
|
|
122
122
|
*/
|
|
123
123
|
botMentioned?: boolean;
|
|
124
124
|
/**
|
|
125
|
-
* True when the
|
|
125
|
+
* True when the cloud names no actor for this event: the synthetic
|
|
126
126
|
* actorExternalId identifies the channel's system, not a person, so
|
|
127
127
|
* nothing downstream may treat it as an identity claim. A delete on a
|
|
128
128
|
* platform whose dispatch carries no author is the canonical case, and so
|
|
129
|
-
* is a delete of the assistant's own post on a
|
|
129
|
+
* is a delete of the assistant's own post on a cloud that names the
|
|
130
130
|
* author but never the deleter. The daemon then applies the event only to
|
|
131
131
|
* a row it already holds for that chat: one it ingested from an author
|
|
132
132
|
* who cleared the ACL on arrival, or one the assistant posted itself.
|
|
@@ -121,7 +121,7 @@ describe("a Comms declaration", () => {
|
|
|
121
121
|
*
|
|
122
122
|
* The two things a single path could not express are both here. The
|
|
123
123
|
* conversation falls back from `message.space.id` to `space.id`, which in code
|
|
124
|
-
* is `message.space ?? event.space`, and the
|
|
124
|
+
* is `message.space ?? event.space`, and the cloud is mapped to the two
|
|
125
125
|
* values admission acts on. The vendor\'s own casing is used for the map key,
|
|
126
126
|
* because a declaration should be able to quote what the wire says.
|
|
127
127
|
*/
|
|
@@ -187,7 +187,7 @@ describe("a Photon declaration", () => {
|
|
|
187
187
|
);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
-
it("maps the
|
|
190
|
+
it("maps the cloud case-insensitively", () => {
|
|
191
191
|
// Photon spells it `iMessage`. A map that missed on capitalisation would
|
|
192
192
|
// silently downgrade every blue bubble to the spoofable-sender treatment.
|
|
193
193
|
const result = read("imessage", photonDelivery(), PHOTON);
|
|
@@ -198,7 +198,7 @@ const INBOUND_FIELD_NAMES = Object.keys(
|
|
|
198
198
|
/**
|
|
199
199
|
* What the sender's id is, so it can be compared with the ones already stored.
|
|
200
200
|
*
|
|
201
|
-
* `opaque` (the default) is right for a
|
|
201
|
+
* `opaque` (the default) is right for a cloud-stable id and is the only
|
|
202
202
|
* safe guess, because rewriting an id that was already canonical is how a
|
|
203
203
|
* returning sender stops matching their own contact record.
|
|
204
204
|
*/
|
|
@@ -664,9 +664,9 @@ describe("HMAC URL and form payloads", () => {
|
|
|
664
664
|
expect(result).toEqual({ ok: true });
|
|
665
665
|
});
|
|
666
666
|
|
|
667
|
-
it("verifies against the
|
|
667
|
+
it("verifies against the cloud-injected URL first", () => {
|
|
668
668
|
const injected =
|
|
669
|
-
"https://
|
|
669
|
+
"https://test-cloud.mycadet.ai.test/v1/gateway/callbacks/abc123";
|
|
670
670
|
const result = verify(
|
|
671
671
|
URL_AND_FORM_HMAC,
|
|
672
672
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Declared signature verification for plugin ingress routes.
|
|
3
3
|
*
|
|
4
|
-
* A plugin route that receives third-party webhooks cannot use the
|
|
4
|
+
* A plugin route that receives third-party webhooks cannot use the cloud
|
|
5
5
|
* scheme (`Cadet-Signature`, see `../http/cadet-signature.ts`): the caller is
|
|
6
6
|
* Comms, or Photon, or Stripe, and it signs the way it signs. Before this,
|
|
7
7
|
* such a route could be declared, approved, registered with the vendor, and
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* - **The credential's service.** A descriptor names a *field*; the service is
|
|
23
23
|
* composed from the plugin's own directory name by the caller. A manifest
|
|
24
24
|
* that could name the service could point a route at another plugin's secret,
|
|
25
|
-
* or at the
|
|
25
|
+
* or at the cloud's.
|
|
26
26
|
* - **Fail-closed parsing.** Anything unrecognized — an unknown `kind`,
|
|
27
27
|
* algorithm, encoding, or an extra key — fails the manifest rather than
|
|
28
|
-
* falling back to the
|
|
28
|
+
* falling back to the cloud scheme. A route the plugin believes is
|
|
29
29
|
* verified one way and the gateway verifies another is worse than no route.
|
|
30
30
|
* - **Canonical payload parts.** `body` is the bytes as received. Other
|
|
31
31
|
* payload parts declare their own canonicalization, so a manifest cannot
|
|
@@ -192,7 +192,7 @@ export type VerificationResult =
|
|
|
192
192
|
/**
|
|
193
193
|
* The public URLs a caller might have signed.
|
|
194
194
|
*
|
|
195
|
-
* The
|
|
195
|
+
* The cloud callback proxy can preserve the original URL explicitly, while
|
|
196
196
|
* a reverse proxy can expose it through forwarding headers. A configured
|
|
197
197
|
* public base and the raw request URL cover direct deployments. Each distinct
|
|
198
198
|
* candidate is tried because only the caller knows which public spelling it
|
|
@@ -404,7 +404,7 @@ describe("findServableRoute", () => {
|
|
|
404
404
|
});
|
|
405
405
|
|
|
406
406
|
it("serves a cadet-signed route without approval", () => {
|
|
407
|
-
// Only a caller holding the
|
|
407
|
+
// Only a caller holding the cloud secret can open it, and that trust
|
|
408
408
|
// was already given when the account was connected.
|
|
409
409
|
const res = resolution({
|
|
410
410
|
pending: [
|
|
@@ -177,7 +177,7 @@ function resolveDiscoveredPluginIngress(
|
|
|
177
177
|
*
|
|
178
178
|
* Approval is the general gate, with one exception: a route declaring
|
|
179
179
|
* `signer: "cadet"` is served without it. Such a route only opens to a
|
|
180
|
-
* caller holding the
|
|
180
|
+
* caller holding the cloud's own webhook secret, which is to say us, and
|
|
181
181
|
* the user extended that trust when they connected their account, so asking
|
|
182
182
|
* them to approve it again buys nothing. A route signed by anyone else still
|
|
183
183
|
* needs a guardian decision, because approval is what establishes who that
|
|
@@ -112,7 +112,7 @@ describe("parsePluginIngressManifest", () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it("defaults an undeclared signer to the plugin's own secret", () => {
|
|
115
|
-
// The safe default: a plugin gets no reach against the
|
|
115
|
+
// The safe default: a plugin gets no reach against the cloud's key
|
|
116
116
|
// unless it asks for it and a guardian approves that declaration.
|
|
117
117
|
const manifest = parsePluginIngressManifest(JSON.parse(VALID));
|
|
118
118
|
expect(manifest.routes[0]!.signer).toBe("plugin");
|
|
@@ -238,7 +238,7 @@ describe("parsePluginIngressManifest", () => {
|
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
it("leaves verification undeclared when the manifest omits it", () => {
|
|
241
|
-
// Absent means the
|
|
241
|
+
// Absent means the cloud scheme, exactly as before the field existed.
|
|
242
242
|
const manifest = parsePluginIngressManifest(JSON.parse(VALID));
|
|
243
243
|
expect(manifest.routes[0]!.verification).toBeUndefined();
|
|
244
244
|
});
|
|
@@ -339,7 +339,7 @@ describe("parsePluginIngressManifest", () => {
|
|
|
339
339
|
});
|
|
340
340
|
|
|
341
341
|
it("rejects a verification descriptor the gateway cannot run", () => {
|
|
342
|
-
// Fail the manifest rather than falling back to the
|
|
342
|
+
// Fail the manifest rather than falling back to the cloud scheme: a
|
|
343
343
|
// route verified differently than declared is worse than no route.
|
|
344
344
|
expect(() =>
|
|
345
345
|
parsePluginIngressManifest({
|
|
@@ -106,7 +106,7 @@ export const IngressRouteSchema = z.object({
|
|
|
106
106
|
* `webhook_secret`, so a plugin can receive third-party callbacks it has
|
|
107
107
|
* arranged itself, and a guardian decides whether that route is served.
|
|
108
108
|
*
|
|
109
|
-
* `cadet` verifies against the
|
|
109
|
+
* `cadet` verifies against the cloud's `webhook_secret`, for routes
|
|
110
110
|
* only Cadet calls, which would otherwise need a per-plugin secret
|
|
111
111
|
* provisioned for a caller we already authenticate. Such a route is
|
|
112
112
|
* served without a guardian approval: the trust it relies on was given
|
|
@@ -120,7 +120,7 @@ export const IngressRouteSchema = z.object({
|
|
|
120
120
|
/**
|
|
121
121
|
* How the caller proves it holds the signing secret.
|
|
122
122
|
*
|
|
123
|
-
* `signed-headers` (the default) is the
|
|
123
|
+
* `signed-headers` (the default) is the cloud's scheme: the signature
|
|
124
124
|
* travels in `Cadet-Signature`, over the body for HTTP and over
|
|
125
125
|
* `<timestamp>.<pathname>` for a WebSocket upgrade.
|
|
126
126
|
*
|
|
@@ -142,7 +142,7 @@ export const IngressRouteSchema = z.object({
|
|
|
142
142
|
/**
|
|
143
143
|
* How a third-party caller's signature is checked, when it is not ours.
|
|
144
144
|
*
|
|
145
|
-
* Absent (the default) means the
|
|
145
|
+
* Absent (the default) means the cloud scheme: `Cadet-Signature` over
|
|
146
146
|
* the body, keyed by whichever `webhook_secret` {@link signer} selects.
|
|
147
147
|
* That is right for a caller Cadet controls and wrong for every other one
|
|
148
148
|
* — Comms signs `X-Osis-Signature`, Photon signs `X-Spectrum-Signature` over
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* admits is a chat, not a person: who may be answered there is the
|
|
15
15
|
* trust-class admission floor's decision downstream.
|
|
16
16
|
*
|
|
17
|
-
* The candidate is neutral on purpose. How a
|
|
17
|
+
* The candidate is neutral on purpose. How a cloud proves "direct" (Discord
|
|
18
18
|
* by the absence of a guild, Telegram by `chat.type`) and "addressed" (a
|
|
19
19
|
* mentions array, text entities, a reply to the bot's post) is the adapter's
|
|
20
20
|
* side of the seam, built in `discord/admit.ts` and `telegram/admit.ts`. This
|
|
21
|
-
* module never reads a
|
|
21
|
+
* module never reads a cloud field.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import type { AdmissionDropLogLevel } from "./admission-drop-log.js";
|
package/src/cli/contacts.ts
CHANGED
|
@@ -204,7 +204,9 @@ export async function executeContactsCommand(
|
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
console.log("");
|
|
207
|
-
console.log(
|
|
207
|
+
console.log(
|
|
208
|
+
`${contacts.length} ${contacts.length === 1 ? "contact" : "contacts"}`,
|
|
209
|
+
);
|
|
208
210
|
return 0;
|
|
209
211
|
}
|
|
210
212
|
|
package/src/cli/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ function printUsage(): void {
|
|
|
15
15
|
console.log("Operator commands for gateway-owned state.");
|
|
16
16
|
console.log("");
|
|
17
17
|
console.log("Commands:");
|
|
18
|
-
console.log(" contacts Manage contact ACL
|
|
18
|
+
console.log(" contacts Manage the contact ACL with list, get, set-risk-threshold");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const args = process.argv.slice(2);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Bound platform identity (assistant, organization, user).
|
|
3
3
|
*
|
|
4
4
|
* Django maps an assistant API key to these ids via
|
|
5
|
-
* `POST /v1/internal/assistants/validate/`. Resolution reads in-memory
|
|
6
|
-
* overrides
|
|
7
|
-
*
|
|
5
|
+
* `POST /v1/internal/assistants/validate/`. Resolution reads the in-memory
|
|
6
|
+
* overrides the validate response populates — nothing else. When the bound
|
|
7
|
+
* ids are empty, or the
|
|
8
8
|
* API key / platform base URL that produced them has changed, the next
|
|
9
9
|
* resolve retries validate (single-flight, with a cooldown after a failed
|
|
10
10
|
* validate attempt for the same credentials).
|
|
@@ -21,30 +21,28 @@ import { readCredentialResult } from "./credential-reader.js";
|
|
|
21
21
|
import { fetchImpl } from "./fetch.js";
|
|
22
22
|
import { getLogger } from "./logger.js";
|
|
23
23
|
|
|
24
|
-
const log = getLogger("
|
|
24
|
+
const log = getLogger("cloud-identity");
|
|
25
25
|
|
|
26
|
-
export const
|
|
27
|
-
"/v1/internal/assistants/validate/";
|
|
26
|
+
export const CLOUD_IDENTITY_VALIDATE_PATH = "/v1/internal/assistants/validate/";
|
|
28
27
|
|
|
29
28
|
const VALIDATE_TIMEOUT_MS = 5_000;
|
|
30
29
|
const ENSURE_COOLDOWN_MS = 10_000;
|
|
31
30
|
|
|
32
31
|
const ASSISTANT_API_KEY_ACCOUNT = credentialKey("cadet", "assistant_api_key");
|
|
33
|
-
const PLATFORM_BASE_URL_ACCOUNT = credentialKey("cadet", "platform_base_url");
|
|
34
32
|
|
|
35
|
-
export type
|
|
33
|
+
export type CloudIdentityIds = {
|
|
36
34
|
assistantId: string;
|
|
37
35
|
organizationId: string;
|
|
38
36
|
userId: string;
|
|
39
37
|
};
|
|
40
38
|
|
|
41
|
-
export type
|
|
39
|
+
export type StoredCloudUserId = {
|
|
42
40
|
userId: string | undefined;
|
|
43
41
|
unreachable: boolean;
|
|
44
42
|
};
|
|
45
43
|
|
|
46
|
-
let
|
|
47
|
-
let
|
|
44
|
+
let cloudAssistantIdOverride: string | undefined;
|
|
45
|
+
let cloudOrganizationIdOverride: string | undefined;
|
|
48
46
|
let platformUserIdOverride: string | undefined;
|
|
49
47
|
let identityBoundToFingerprint: string | undefined;
|
|
50
48
|
let lastAttemptFingerprint: string | undefined;
|
|
@@ -65,42 +63,38 @@ function envTrim(name: string): string {
|
|
|
65
63
|
return process.env[name]?.trim() ?? "";
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
function
|
|
69
|
-
return (
|
|
70
|
-
platformAssistantIdOverride || envTrim("PLATFORM_ASSISTANT_ID")
|
|
71
|
-
).trim();
|
|
66
|
+
function getCloudAssistantId(): string {
|
|
67
|
+
return (cloudAssistantIdOverride ?? "").trim();
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
function
|
|
75
|
-
return (
|
|
76
|
-
platformOrganizationIdOverride || envTrim("PLATFORM_ORGANIZATION_ID")
|
|
77
|
-
).trim();
|
|
70
|
+
function getCloudOrganizationId(): string {
|
|
71
|
+
return (cloudOrganizationIdOverride ?? "").trim();
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
function
|
|
81
|
-
return (platformUserIdOverride
|
|
74
|
+
function getCloudUserId(): string {
|
|
75
|
+
return (platformUserIdOverride ?? "").trim();
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
function hasBoundIdentity(): boolean {
|
|
85
|
-
return Boolean(
|
|
79
|
+
return Boolean(getCloudAssistantId() && getCloudUserId());
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
function identityFingerprint(apiKey: string, baseUrl: string): string {
|
|
89
83
|
return `${baseUrl}\0${apiKey}`;
|
|
90
84
|
}
|
|
91
85
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
function clearCloudIdentityOverrides(): void {
|
|
87
|
+
cloudAssistantIdOverride = undefined;
|
|
88
|
+
cloudOrganizationIdOverride = undefined;
|
|
95
89
|
platformUserIdOverride = undefined;
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
export function
|
|
92
|
+
export function applyCloudIdentityIds(ids: CloudIdentityIds): void {
|
|
99
93
|
if (ids.assistantId) {
|
|
100
|
-
|
|
94
|
+
cloudAssistantIdOverride = ids.assistantId;
|
|
101
95
|
}
|
|
102
96
|
if (ids.organizationId) {
|
|
103
|
-
|
|
97
|
+
cloudOrganizationIdOverride = ids.organizationId;
|
|
104
98
|
}
|
|
105
99
|
if (ids.userId) {
|
|
106
100
|
platformUserIdOverride = ids.userId;
|
|
@@ -111,16 +105,16 @@ export function applyPlatformIdentityIds(ids: PlatformIdentityIds): void {
|
|
|
111
105
|
* Use the gateway credential cache for API key / base URL lookups so a
|
|
112
106
|
* vault outage can still retry validate with last-good secrets.
|
|
113
107
|
*/
|
|
114
|
-
export function
|
|
108
|
+
export function bindCloudIdentityCredentialCache(
|
|
115
109
|
cache: CredentialCache | undefined,
|
|
116
110
|
): void {
|
|
117
111
|
credentialCache = cache;
|
|
118
112
|
}
|
|
119
113
|
|
|
120
114
|
/** @internal Test-only: drop in-memory identity and in-flight ensure state. */
|
|
121
|
-
export function
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
export function _resetCloudIdentityForTests(): void {
|
|
116
|
+
cloudAssistantIdOverride = undefined;
|
|
117
|
+
cloudOrganizationIdOverride = undefined;
|
|
124
118
|
platformUserIdOverride = undefined;
|
|
125
119
|
identityBoundToFingerprint = undefined;
|
|
126
120
|
lastAttemptFingerprint = undefined;
|
|
@@ -132,20 +126,20 @@ export function _resetPlatformIdentityForTests(): void {
|
|
|
132
126
|
|
|
133
127
|
/** @internal Alias so existing auth tests can reset the bound owner id. */
|
|
134
128
|
export function _resetLastKnownPlatformUserIdForTest(): void {
|
|
135
|
-
|
|
129
|
+
_resetCloudIdentityForTests();
|
|
136
130
|
}
|
|
137
131
|
|
|
138
132
|
/**
|
|
139
133
|
* Trade an assistant API key for the bound platform ids.
|
|
140
134
|
*
|
|
141
|
-
* Returns null when the
|
|
135
|
+
* Returns null when the cloud is unreachable, refuses the key, or the
|
|
142
136
|
* body has no ids. Never throws.
|
|
143
137
|
*/
|
|
144
|
-
export async function
|
|
138
|
+
export async function fetchCloudIdentityIds(
|
|
145
139
|
baseUrl: string,
|
|
146
140
|
apiKey: string,
|
|
147
|
-
): Promise<
|
|
148
|
-
const url = `${baseUrl.replace(/\/+$/, "")}${
|
|
141
|
+
): Promise<CloudIdentityIds | null> {
|
|
142
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${CLOUD_IDENTITY_VALIDATE_PATH}`;
|
|
149
143
|
try {
|
|
150
144
|
const res = await fetchImpl(url, {
|
|
151
145
|
method: "POST",
|
|
@@ -211,20 +205,18 @@ async function readAuthPrerequisites(): Promise<{
|
|
|
211
205
|
unreachable: boolean;
|
|
212
206
|
}> {
|
|
213
207
|
const envKey = envTrim("ASSISTANT_API_KEY");
|
|
214
|
-
const envUrl = envTrim("
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return { apiKey, baseUrl, unreachable };
|
|
208
|
+
const envUrl = envTrim("CADET_CLOUD_URL").replace(/\/+$/, "");
|
|
209
|
+
|
|
210
|
+
const apiKeyRead = await readCredentialOrEnv(
|
|
211
|
+
ASSISTANT_API_KEY_ACCOUNT,
|
|
212
|
+
envKey,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
apiKey: apiKeyRead.value,
|
|
217
|
+
baseUrl: envUrl,
|
|
218
|
+
unreachable: !apiKeyRead.value && apiKeyRead.unreachable,
|
|
219
|
+
};
|
|
228
220
|
}
|
|
229
221
|
|
|
230
222
|
/**
|
|
@@ -236,7 +228,7 @@ async function readAuthPrerequisites(): Promise<{
|
|
|
236
228
|
* validate for the same credentials is still inside the cooldown window.
|
|
237
229
|
* Concurrent callers share one in-flight request.
|
|
238
230
|
*/
|
|
239
|
-
export async function
|
|
231
|
+
export async function ensureCloudIdentityIds(): Promise<void> {
|
|
240
232
|
if (!ensureInFlight) {
|
|
241
233
|
ensureInFlight = (async () => {
|
|
242
234
|
try {
|
|
@@ -258,7 +250,7 @@ export async function ensurePlatformIdentityIds(): Promise<void> {
|
|
|
258
250
|
identityBoundToFingerprint !== undefined &&
|
|
259
251
|
identityBoundToFingerprint !== fingerprint
|
|
260
252
|
) {
|
|
261
|
-
|
|
253
|
+
clearCloudIdentityOverrides();
|
|
262
254
|
identityBoundToFingerprint = undefined;
|
|
263
255
|
}
|
|
264
256
|
if (
|
|
@@ -268,19 +260,19 @@ export async function ensurePlatformIdentityIds(): Promise<void> {
|
|
|
268
260
|
return;
|
|
269
261
|
}
|
|
270
262
|
lastAttemptFingerprint = fingerprint;
|
|
271
|
-
const ids = await
|
|
263
|
+
const ids = await fetchCloudIdentityIds(baseUrl, apiKey);
|
|
272
264
|
if (!ids) {
|
|
273
265
|
nextEnsureAttemptAt = Date.now() + ENSURE_COOLDOWN_MS;
|
|
274
266
|
return;
|
|
275
267
|
}
|
|
276
|
-
|
|
268
|
+
applyCloudIdentityIds(ids);
|
|
277
269
|
identityBoundToFingerprint = fingerprint;
|
|
278
270
|
nextEnsureAttemptAt = 0;
|
|
279
271
|
log.info(
|
|
280
272
|
{
|
|
281
|
-
hasAssistantId: Boolean(
|
|
282
|
-
hasOrganizationId: Boolean(
|
|
283
|
-
hasUserId: Boolean(
|
|
273
|
+
hasAssistantId: Boolean(getCloudAssistantId()),
|
|
274
|
+
hasOrganizationId: Boolean(getCloudOrganizationId()),
|
|
275
|
+
hasUserId: Boolean(getCloudUserId()),
|
|
284
276
|
},
|
|
285
277
|
"Loaded platform identity from platform validate",
|
|
286
278
|
);
|
|
@@ -292,26 +284,26 @@ export async function ensurePlatformIdentityIds(): Promise<void> {
|
|
|
292
284
|
await ensureInFlight;
|
|
293
285
|
}
|
|
294
286
|
|
|
295
|
-
export async function
|
|
296
|
-
await
|
|
297
|
-
return
|
|
287
|
+
export async function resolveCloudAssistantId(): Promise<string> {
|
|
288
|
+
await ensureCloudIdentityIds();
|
|
289
|
+
return getCloudAssistantId();
|
|
298
290
|
}
|
|
299
291
|
|
|
300
|
-
export function
|
|
301
|
-
const id =
|
|
292
|
+
export function peekCloudAssistantId(): string | undefined {
|
|
293
|
+
const id = getCloudAssistantId();
|
|
302
294
|
return id || undefined;
|
|
303
295
|
}
|
|
304
296
|
|
|
305
|
-
export async function
|
|
297
|
+
export async function resolveCloudAssistantIdOrUndefined(): Promise<
|
|
306
298
|
string | undefined
|
|
307
299
|
> {
|
|
308
|
-
const id = await
|
|
300
|
+
const id = await resolveCloudAssistantId();
|
|
309
301
|
return id || undefined;
|
|
310
302
|
}
|
|
311
303
|
|
|
312
304
|
export async function resolvePlatformUserId(): Promise<string> {
|
|
313
|
-
await
|
|
314
|
-
return
|
|
305
|
+
await ensureCloudIdentityIds();
|
|
306
|
+
return getCloudUserId();
|
|
315
307
|
}
|
|
316
308
|
|
|
317
309
|
/**
|
|
@@ -322,7 +314,7 @@ export async function resolvePlatformUserId(): Promise<string> {
|
|
|
322
314
|
* prior successful resolve is cached. A cached owner is returned with
|
|
323
315
|
* `unreachable: false` so callers keep treating the assistant as reachable.
|
|
324
316
|
*/
|
|
325
|
-
export async function
|
|
317
|
+
export async function readStoredCloudUserId(): Promise<StoredCloudUserId> {
|
|
326
318
|
const userId = await resolvePlatformUserId();
|
|
327
319
|
if (userId) {
|
|
328
320
|
return { userId, unreachable: false };
|
package/src/cloud-url.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the cloud base URL from the `CADET_CLOUD_URL` environment
|
|
3
|
+
* variable, defaulting to `https://cloud.mycadet.ai`.
|
|
4
|
+
*
|
|
5
|
+
* The returned URL is trimmed and has any trailing slashes removed so callers
|
|
6
|
+
* can safely append path segments.
|
|
7
|
+
*/
|
|
8
|
+
export function getCloudBaseUrl(): string {
|
|
9
|
+
const url = (process.env.CADET_CLOUD_URL?.trim() || "").replace(/\/+$/, "");
|
|
10
|
+
return url || "https://cloud.mycadet.ai";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function str(name: string): string | undefined {
|
|
14
|
+
const v = process.env[name]?.trim();
|
|
15
|
+
return v || undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function flag(name: string): boolean {
|
|
19
|
+
const raw = str(name)?.toLowerCase();
|
|
20
|
+
return raw === "true" || raw === "1";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getIsCloud(): boolean {
|
|
24
|
+
return flag("IS_CLOUD");
|
|
25
|
+
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Bound platform owner id for managed-mode edge auth.
|
|
3
3
|
*
|
|
4
4
|
* Identity is resolved from platform validate and in-memory last-known
|
|
5
|
-
* values. See {@link
|
|
5
|
+
* values. See {@link readStoredCloudUserId} in `cloud-identity.ts`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export {
|
|
9
9
|
_resetLastKnownPlatformUserIdForTest,
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
} from "./
|
|
10
|
+
readStoredCloudUserId,
|
|
11
|
+
type StoredCloudUserId,
|
|
12
|
+
} from "./cloud-identity.js";
|
package/src/config.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type GatewayConfig = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Body cap for the inbound email webhook (`/webhooks/email`). Email
|
|
27
27
|
* attachments arrive inlined as base64 in the payload, so this ceiling is
|
|
28
|
-
* much larger than {@link maxWebhookPayloadBytes} to fit the
|
|
28
|
+
* much larger than {@link maxWebhookPayloadBytes} to fit the cloud's
|
|
29
29
|
* per-file / per-message attachment cap. Optional — callers fall back to
|
|
30
30
|
* {@link maxWebhookPayloadBytes} when unset.
|
|
31
31
|
*/
|
|
@@ -36,7 +36,6 @@ export type GatewayConfig = {
|
|
|
36
36
|
runtimeMaxRetries: number;
|
|
37
37
|
runtimeProxyRequireAuth: boolean;
|
|
38
38
|
runtimeTimeoutMs: number;
|
|
39
|
-
shutdownDrainMs: number;
|
|
40
39
|
/** When true, trust X-Forwarded-For for client IP resolution (set when behind a reverse proxy). */
|
|
41
40
|
trustProxy: boolean;
|
|
42
41
|
};
|
|
@@ -217,7 +216,7 @@ export function loadConfig(): GatewayConfig {
|
|
|
217
216
|
},
|
|
218
217
|
maxAttachmentConcurrency: 3,
|
|
219
218
|
maxWebhookPayloadBytes: 1024 * 1024,
|
|
220
|
-
// Fits the
|
|
219
|
+
// Fits the cloud's inbound-attachment cap (25 MB/file × 10 files),
|
|
221
220
|
// expanded by base64 (~4/3) plus body/JSON overhead.
|
|
222
221
|
maxEmailWebhookPayloadBytes: 350 * 1024 * 1024,
|
|
223
222
|
port,
|
|
@@ -226,7 +225,6 @@ export function loadConfig(): GatewayConfig {
|
|
|
226
225
|
runtimeMaxRetries: 2,
|
|
227
226
|
runtimeProxyRequireAuth,
|
|
228
227
|
runtimeTimeoutMs,
|
|
229
|
-
shutdownDrainMs: 5000,
|
|
230
228
|
trustProxy,
|
|
231
229
|
};
|
|
232
230
|
}
|
package/src/credential-reader.ts
CHANGED
|
@@ -196,7 +196,7 @@ export function getCesHttpConfig():
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Try to read a credential from the CES
|
|
199
|
+
* Try to read a credential from the CES cloud service over HTTP.
|
|
200
200
|
*
|
|
201
201
|
* Delegates to `@mycadet/ces-client/http-credentials` for the transport.
|
|
202
202
|
* Activated when `CES_CREDENTIAL_URL` is set (e.g. `http://ces-host:8090`).
|
|
@@ -404,7 +404,7 @@ export const DISCORD_CHANNEL_CREDENTIAL_SPEC: ServiceCredentialSpec = {
|
|
|
404
404
|
|
|
405
405
|
export const CADET_CREDENTIAL_SPEC: ServiceCredentialSpec = {
|
|
406
406
|
service: "cadet",
|
|
407
|
-
requiredFields: ["
|
|
407
|
+
requiredFields: ["assistant_api_key", "webhook_secret"],
|
|
408
408
|
} as const;
|
|
409
409
|
|
|
410
410
|
export const ALL_CREDENTIAL_SPECS: readonly ServiceCredentialSpec[] = [
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SQLite database via IPC. Gateway one-time data migrations (m0002, m0010,
|
|
4
4
|
* m0014, et al.) need raw read/drop access to the assistant DB on first boot
|
|
5
5
|
* of upgraded installs, and direct cross-container file access corrupts the
|
|
6
|
-
* DB on
|
|
6
|
+
* DB on cloud pods (fcntl locks are not shared across mount namespaces +
|
|
7
7
|
* a SQLite WAL-reset bug in ≤3.51.2) — so migrations reach the assistant DB
|
|
8
8
|
* only through this route.
|
|
9
9
|
*
|
package/src/discord/forward.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function createDiscordInboundEventHandler(options: {
|
|
|
24
24
|
const { config, log, notifyRecordActivity, forwardQueue } = options;
|
|
25
25
|
|
|
26
26
|
return (event, attachmentRefs) => {
|
|
27
|
-
// Reset the
|
|
27
|
+
// Reset the cloud idle-sleep timer so inbound Discord activity keeps
|
|
28
28
|
// the assistant awake like any other channel.
|
|
29
29
|
notifyRecordActivity();
|
|
30
30
|
|
package/src/email/normalize.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface EmailAttachment {
|
|
|
24
24
|
* Shape of a normalized inbound email event as sent by the Cadet
|
|
25
25
|
* platform (or any upstream caller).
|
|
26
26
|
*
|
|
27
|
-
* The
|
|
27
|
+
* The cloud is responsible for provider-specific parsing (e.g.
|
|
28
28
|
* Mailgun multipart → JSON). By the time the payload reaches the
|
|
29
29
|
* gateway it should already be in this canonical shape.
|
|
30
30
|
*/
|
|
@@ -47,15 +47,15 @@ export interface CadetEmailPayload {
|
|
|
47
47
|
inReplyTo?: string;
|
|
48
48
|
/** Space-separated chain of ancestor Message-IDs (References header). */
|
|
49
49
|
references?: string;
|
|
50
|
-
/** Stable conversation/thread identifier derived by the
|
|
50
|
+
/** Stable conversation/thread identifier derived by the cloud. */
|
|
51
51
|
conversationId: string;
|
|
52
52
|
/** ISO 8601 timestamp of the original email. */
|
|
53
53
|
timestamp?: string;
|
|
54
54
|
/**
|
|
55
|
-
* Whether the
|
|
55
|
+
* Whether the cloud authenticated the sender's `From:` address against
|
|
56
56
|
* the message's SPF/DKIM/DMARC results. `true` when authentication passed,
|
|
57
57
|
* `false` when it was evaluated and failed (spoofable sender), omitted when
|
|
58
|
-
* the
|
|
58
|
+
* the cloud could not evaluate (no auth-results header). The gateway
|
|
59
59
|
* downgrades an unauthenticated sender so a forged `From:` cannot inherit
|
|
60
60
|
* guardian/trusted_contact trust.
|
|
61
61
|
*/
|