@almadar/integrations 2.25.0 → 2.26.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.
@@ -1,6 +1,8 @@
1
1
  import { createLogger } from '@almadar/logger';
2
2
  import { integratorsRegistry } from '@almadar/core/patterns';
3
3
  import { createHmac, createHash } from 'crypto';
4
+ import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
5
+ import { join } from 'path';
4
6
  import Stripe2 from 'stripe';
5
7
  import { google } from 'googleapis';
6
8
  import twilio from 'twilio';
@@ -11,8 +13,6 @@ import { Readable } from 'stream';
11
13
  import { getAvailableProvider, LLMClient, EmbeddingClient } from '@almadar/llm';
12
14
  import { z } from 'zod';
13
15
  import { execSync, spawn } from 'child_process';
14
- import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
15
- import { join } from 'path';
16
16
  import { tmpdir } from 'os';
17
17
  import * as oidc from 'openid-client';
18
18
  import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
@@ -39,6 +39,133 @@ var IntegrationError = class extends Error {
39
39
  };
40
40
  }
41
41
  };
42
+
43
+ // src/contracts.ts
44
+ var serviceCredentials = {
45
+ stripe: [
46
+ { envVar: "STRIPE_SECRET_KEY", required: true, description: "Stripe secret API key" },
47
+ { envVar: "STRIPE_WEBHOOK_SECRET", required: false, description: "Webhook endpoint signing secret" }
48
+ ],
49
+ youtube: [
50
+ { envVar: "YOUTUBE_API_KEY", required: true, description: "YouTube Data API key" }
51
+ ],
52
+ twilio: [
53
+ { envVar: "TWILIO_ACCOUNT_SID", required: true, description: "Twilio account SID" },
54
+ { envVar: "TWILIO_AUTH_TOKEN", required: true, description: "Twilio auth token" },
55
+ { envVar: "TWILIO_PHONE_NUMBER", required: false, description: "Default sender phone number" }
56
+ ],
57
+ email: [
58
+ { envVar: "SENDGRID_API_KEY", required: false, description: "SendGrid API key (use this OR RESEND_API_KEY)" },
59
+ { envVar: "RESEND_API_KEY", required: false, description: "Resend API key (use this OR SENDGRID_API_KEY)" },
60
+ { envVar: "FROM_EMAIL", required: false, description: "Default sender email address" }
61
+ ],
62
+ webhook: [
63
+ { envVar: "WEBHOOK_SIGNING_SECRET", required: false, description: "Default HMAC-SHA256 signing secret (per-call secret overrides)" },
64
+ { envVar: "WEBHOOK_TIMEOUT_MS", required: false, description: "Per-request timeout (ms, default 10000)" }
65
+ ],
66
+ push: [
67
+ { envVar: "VAPID_PUBLIC_KEY", required: true, description: "VAPID public key (web-push generate-vapid-keys)" },
68
+ { envVar: "VAPID_PRIVATE_KEY", required: true, description: "VAPID private key" },
69
+ { envVar: "VAPID_SUBJECT", required: true, description: "VAPID subject (mailto: or https: contact URI)" }
70
+ ],
71
+ calendar: [
72
+ { envVar: "GOOGLE_CALENDAR_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with calendar scope; store in Secret Manager, bind as env" },
73
+ { envVar: "GOOGLE_CALENDAR_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
74
+ { envVar: "GOOGLE_CALENDAR_ID", required: false, description: "Default calendar id (default: primary)" },
75
+ { envVar: "GOOGLE_CALENDAR_CHANNEL_TOKEN", required: false, description: "Watch-channel verification token \u2014 required to receive inbound calendar hooks (two-way sync)" }
76
+ ],
77
+ drive: [
78
+ { envVar: "GOOGLE_DRIVE_SA_KEY", required: false, description: "Google service-account key JSON (raw or base64) with drive scope \u2014 serves reads; store in Secret Manager, bind as env" },
79
+ { envVar: "GOOGLE_DRIVE_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
80
+ { envVar: "GOOGLE_DRIVE_REFRESH_TOKEN", required: false, description: "User OAuth refresh token (drive-consent.mjs) \u2014 serves writes; SA uploads are impossible on personal accounts (no SA storage quota)" },
81
+ { envVar: "GOOGLE_DRIVE_FOLDER_ID", required: false, description: "Default parent folder for uploads/new folders when the call names none" }
82
+ ],
83
+ metaAds: [
84
+ { envVar: "META_ACCESS_TOKEN", required: true, description: "Meta Graph API access token (Marketing API, ads_read)" },
85
+ { envVar: "META_AD_ACCOUNT_ID", required: false, description: "Default ad account id (act_\u2026); per-call accountId overrides" }
86
+ ],
87
+ accounting: [],
88
+ banking: [
89
+ { envVar: "GOCARDLESS_SECRET_ID", required: true, description: "GoCardless Bank Account Data secret id" },
90
+ { envVar: "GOCARDLESS_SECRET_KEY", required: true, description: "GoCardless Bank Account Data secret key" }
91
+ ],
92
+ esign: [
93
+ { envVar: "DOCUSIGN_BASE_URL", required: true, description: "DocuSign REST base (e.g. https://demo.docusign.net/restapi/v2.1/accounts/<accountId>)" },
94
+ { envVar: "DOCUSIGN_ACCESS_TOKEN", required: true, description: "DocuSign OAuth access token (JWT grant rotation is the deployment concern)" }
95
+ ],
96
+ llm: [
97
+ { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key (use this OR OPENAI_API_KEY)" },
98
+ { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key (use this OR ANTHROPIC_API_KEY)" }
99
+ ],
100
+ "llm-integration": [
101
+ { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key" },
102
+ { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key" }
103
+ ],
104
+ ml: [
105
+ { envVar: "MASAR_URL", required: true, description: "Base URL of the deployed model-serving orbital" },
106
+ { envVar: "MASAR_ML_TRAIT", required: true, description: "Kebab-case trait name the serving orbital exposes its /events route under" }
107
+ ],
108
+ deepagent: [
109
+ { envVar: "DEEPAGENT_API_URL", required: true, description: "DeepAgent server URL" },
110
+ { envVar: "DEEPAGENT_API_KEY", required: false, description: "DeepAgent API key" }
111
+ ],
112
+ github: [
113
+ { envVar: "GITHUB_TOKEN", required: true, description: "GitHub personal access token" },
114
+ { envVar: "GITHUB_OWNER", required: false, description: "Default repository owner" },
115
+ { envVar: "GITHUB_REPO", required: false, description: "Default repository name" }
116
+ ],
117
+ docker: [
118
+ { envVar: "DOCKER_HOST", required: false, description: "Docker daemon host URL" }
119
+ ],
120
+ storage: [
121
+ { envVar: "STORAGE_ACCESS_KEY_ID", required: true, description: "S3-compatible access key id" },
122
+ { envVar: "STORAGE_SECRET_ACCESS_KEY", required: true, description: "S3-compatible secret access key" },
123
+ { envVar: "STORAGE_BUCKET", required: true, description: "Default bucket name" },
124
+ { envVar: "STORAGE_REGION", required: false, description: "Region (default us-east-1)" },
125
+ { envVar: "STORAGE_ENDPOINT", required: false, description: "Custom S3-compatible endpoint (R2/MinIO/\u2026); empty = AWS S3" },
126
+ { envVar: "STORAGE_PUBLIC_URL_BASE", required: false, description: "Base URL for public-acl object links; empty = endpoint-derived" }
127
+ ],
128
+ queue: [
129
+ { envVar: "QUEUE_URL", required: true, description: "Message queue connection URL" }
130
+ ],
131
+ redis: [
132
+ { envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
133
+ ],
134
+ oauth: [
135
+ { envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
136
+ { envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
137
+ { envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
138
+ { envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
139
+ ],
140
+ credentials: [
141
+ { envVar: "ALMADAR_CREDENTIAL_MASTER_KEY", required: false, description: "AES-256 master key (64-char hex) enabling the hosted credential store \u2014 hold it alone in the platform secret store" },
142
+ { envVar: "ALMADAR_CREDENTIAL_MASTER_KEY_PREVIOUS", required: false, description: "Previous master key, present only during a rotation window \u2014 decrypts old rows until `credentials.rotate` re-encrypts them" }
143
+ ],
144
+ otel: [
145
+ { envVar: "OTEL_EXPORTER_OTLP_ENDPOINT", required: true, description: "OpenTelemetry collector endpoint" },
146
+ { envVar: "OTEL_SERVICE_NAME", required: false, description: "Service name for traces" }
147
+ ],
148
+ cli: [],
149
+ // No fixed env vars — connection strings are resolved per-query from the
150
+ // caller-supplied connectionRef, so credentials cannot be declared statically.
151
+ database: [],
152
+ wikimedia: [
153
+ { envVar: "WIKIMEDIA_USER_AGENT", required: false, description: "Descriptive User-Agent for the Wikipedia API" },
154
+ { envVar: "WIKIMEDIA_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
155
+ ],
156
+ iconify: [
157
+ { envVar: "ICONIFY_USER_AGENT", required: false, description: "User-Agent for the Iconify API" },
158
+ { envVar: "ICONIFY_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
159
+ ],
160
+ arxiv: [
161
+ { envVar: "ARXIV_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
162
+ ]
163
+ };
164
+ var serviceProbes = {
165
+ calendar: { action: "listEvents", params: { maxResults: 1 } },
166
+ drive: { action: "listFiles", params: { maxResults: 1 } },
167
+ metaAds: { action: "listCampaigns", params: {} }
168
+ };
42
169
  var ConsoleLogger = class {
43
170
  constructor(_level = "info") {
44
171
  this.log = createLogger("almadar:integrations");
@@ -56,6 +183,7 @@ var ConsoleLogger = class {
56
183
  this.log.error(message, meta);
57
184
  }
58
185
  };
186
+ var RESERVED_ENVELOPE_KEYS = /* @__PURE__ */ new Set(["emit", "onSuccess", "onError", "timeout"]);
59
187
  function validateParams(integration, action, params) {
60
188
  const typedRegistry = integratorsRegistry;
61
189
  const registry = typedRegistry.integrators[integration];
@@ -78,6 +206,16 @@ function validateParams(integration, action, params) {
78
206
  };
79
207
  }
80
208
  const errors = [];
209
+ const declaredNames = new Set(actionDef.params.map((p) => p.name));
210
+ for (const key of Object.keys(params)) {
211
+ if (RESERVED_ENVELOPE_KEYS.has(key)) continue;
212
+ if (!declaredNames.has(key)) {
213
+ errors.push({
214
+ param: key,
215
+ message: `Unknown parameter: ${key} (declared: ${[...declaredNames].sort().join(", ")})`
216
+ });
217
+ }
218
+ }
81
219
  for (const paramDef of actionDef.params) {
82
220
  if (paramDef.required && !(paramDef.name in params)) {
83
221
  errors.push({
@@ -267,7 +405,7 @@ var IntegrationFactory = class {
267
405
  */
268
406
  async execute(integration, action, params, context) {
269
407
  const instance = this.get(integration, context?.principal);
270
- return await instance.execute(action, params);
408
+ return await instance.execute(action, params, context);
271
409
  }
272
410
  /**
273
411
  * Check if integration is configured
@@ -330,6 +468,7 @@ function getInstalledCredentialStore() {
330
468
  function resolveCredentialRef(ref, env = process.env) {
331
469
  return installedStore?.resolve(ref) ?? env[ref];
332
470
  }
471
+ join(".almadar", "dev-credentials.json");
333
472
 
334
473
  // src/integrations/stripe/index.ts
335
474
  var STRIPE_API_VERSION = "2025-02-24.acacia";
@@ -774,10 +913,10 @@ var TwilioIntegration = class extends BaseIntegration {
774
913
  }
775
914
  }
776
915
  async sendSMS(params) {
777
- const { to, body } = params;
916
+ const { to, body, from } = params;
778
917
  this.logger.debug("Sending SMS", { to: String(to ?? "") });
779
918
  const message = await this.client.messages.create({
780
- from: this.phoneNumber,
919
+ from: from || this.phoneNumber,
781
920
  to,
782
921
  body
783
922
  });
@@ -856,31 +995,35 @@ var EmailIntegration = class extends BaseIntegration {
856
995
  }
857
996
  }
858
997
  async send(params) {
859
- const { to, subject, body, from } = params;
998
+ const { to, subject, body, from, htmlBody, replyTo, templateId } = params;
860
999
  this.logger.debug("Sending email", { to: String(to), subject: String(subject), provider: this.provider });
1000
+ const message = {
1001
+ to,
1002
+ subject,
1003
+ body,
1004
+ from: from || this.fromEmail,
1005
+ htmlBody: htmlBody || void 0,
1006
+ replyTo: replyTo || void 0,
1007
+ templateId: templateId || void 0
1008
+ };
861
1009
  if (this.provider === "sendgrid") {
862
- return await this.sendViaSendGrid(
863
- to,
864
- subject,
865
- body,
866
- from || this.fromEmail
867
- );
1010
+ return await this.sendViaSendGrid(message);
868
1011
  } else if (this.provider === "resend") {
869
- return await this.sendViaResend(
870
- to,
871
- subject,
872
- body,
873
- from || this.fromEmail
874
- );
1012
+ return await this.sendViaResend(message);
875
1013
  }
876
1014
  throw new Error(`Unknown email provider: ${this.provider}`);
877
1015
  }
878
- async sendViaSendGrid(to, subject, body, from) {
1016
+ async sendViaSendGrid(message) {
879
1017
  const msg = {
880
- to,
881
- from,
882
- subject,
883
- html: body
1018
+ to: message.to,
1019
+ from: message.from,
1020
+ subject: message.subject,
1021
+ // htmlBody present → it carries the HTML and body becomes the
1022
+ // plain-text alternative; absent → body renders as HTML (legacy).
1023
+ html: message.htmlBody ?? message.body,
1024
+ ...message.htmlBody ? { text: message.body } : {},
1025
+ ...message.replyTo ? { replyTo: message.replyTo } : {},
1026
+ ...message.templateId ? { templateId: message.templateId } : {}
884
1027
  };
885
1028
  const response = await sgMail.send(msg);
886
1029
  return {
@@ -888,15 +1031,20 @@ var EmailIntegration = class extends BaseIntegration {
888
1031
  status: "sent"
889
1032
  };
890
1033
  }
891
- async sendViaResend(to, subject, body, from) {
1034
+ async sendViaResend(message) {
892
1035
  if (!this.resendClient) {
893
1036
  throw new Error("Resend client not initialized");
894
1037
  }
1038
+ if (message.templateId) {
1039
+ throw new Error("templateId is not supported by the resend provider \u2014 use sendgrid or drop templateId");
1040
+ }
895
1041
  const response = await this.resendClient.emails.send({
896
- from,
897
- to,
898
- subject,
899
- html: body
1042
+ from: message.from,
1043
+ to: message.to,
1044
+ subject: message.subject,
1045
+ html: message.htmlBody ?? message.body,
1046
+ ...message.htmlBody ? { text: message.body } : {},
1047
+ ...message.replyTo ? { replyTo: message.replyTo } : {}
900
1048
  });
901
1049
  return {
902
1050
  id: response.data?.id,
@@ -1298,20 +1446,53 @@ var DriveIntegration = class extends BaseIntegration {
1298
1446
  constructor(config) {
1299
1447
  super(config);
1300
1448
  const rawKey = config.env.GOOGLE_DRIVE_SA_KEY;
1301
- if (!rawKey) {
1302
- throw new Error("GOOGLE_DRIVE_SA_KEY not configured");
1449
+ if (rawKey) {
1450
+ const keyJson = rawKey.trim().startsWith("{") ? rawKey : Buffer.from(rawKey, "base64").toString("utf8");
1451
+ const key = JSON.parse(keyJson);
1452
+ const subject = config.env.GOOGLE_DRIVE_SUBJECT || void 0;
1453
+ const auth = new google.auth.JWT({
1454
+ email: key.client_email,
1455
+ key: key.private_key,
1456
+ scopes: ["https://www.googleapis.com/auth/drive"],
1457
+ subject
1458
+ });
1459
+ this.saClient = google.drive({ version: "v3", auth });
1460
+ } else {
1461
+ this.saClient = null;
1462
+ }
1463
+ const refreshToken = config.env.GOOGLE_DRIVE_REFRESH_TOKEN;
1464
+ const clientId = config.env.OAUTH_CLIENT_ID;
1465
+ const clientSecret = config.env.OAUTH_CLIENT_SECRET;
1466
+ if (refreshToken && clientId && clientSecret) {
1467
+ const oauth2 = new google.auth.OAuth2(clientId, clientSecret);
1468
+ oauth2.setCredentials({ refresh_token: refreshToken });
1469
+ this.userClient = google.drive({ version: "v3", auth: oauth2 });
1470
+ } else {
1471
+ this.userClient = null;
1303
1472
  }
1304
- const keyJson = rawKey.trim().startsWith("{") ? rawKey : Buffer.from(rawKey, "base64").toString("utf8");
1305
- const key = JSON.parse(keyJson);
1306
- const subject = config.env.GOOGLE_DRIVE_SUBJECT || void 0;
1307
- const auth = new google.auth.JWT({
1308
- email: key.client_email,
1309
- key: key.private_key,
1310
- scopes: ["https://www.googleapis.com/auth/drive"],
1311
- subject
1473
+ if (!this.saClient && !this.userClient) {
1474
+ throw new Error(
1475
+ "Drive not configured \u2014 set GOOGLE_DRIVE_SA_KEY (reads) and/or GOOGLE_DRIVE_REFRESH_TOKEN + OAUTH_CLIENT_ID/SECRET (writes)"
1476
+ );
1477
+ }
1478
+ this.defaultFolderId = config.env.GOOGLE_DRIVE_FOLDER_ID || void 0;
1479
+ this.logger.info("Drive integration initialized", {
1480
+ serviceAccount: this.saClient !== null,
1481
+ userToken: this.userClient !== null,
1482
+ delegated: Boolean(config.env.GOOGLE_DRIVE_SUBJECT)
1312
1483
  });
1313
- this.client = google.drive({ version: "v3", auth });
1314
- this.logger.info("Drive integration initialized", { delegated: Boolean(subject) });
1484
+ }
1485
+ /** Reads prefer the SA client (delegation-aware); user client covers its absence. */
1486
+ readClient() {
1487
+ const client = this.saClient ?? this.userClient;
1488
+ if (!client) throw new Error("Drive not configured");
1489
+ return client;
1490
+ }
1491
+ /** Writes REQUIRE the user client on personal accounts (SA has no storage quota); SA only as a Workspace fallback. */
1492
+ writeClient() {
1493
+ const client = this.userClient ?? this.saClient;
1494
+ if (!client) throw new Error("Drive not configured");
1495
+ return client;
1315
1496
  }
1316
1497
  async execute(action, params) {
1317
1498
  const validation = this.validateParams(action, params);
@@ -1363,7 +1544,7 @@ var DriveIntegration = class extends BaseIntegration {
1363
1544
  const clauses = ["trashed = false"];
1364
1545
  if (folderId) clauses.push(`'${String(folderId).replace(/'/g, "\\'")}' in parents`);
1365
1546
  if (query) clauses.push(String(query));
1366
- const response = await this.client.files.list({
1547
+ const response = await this.readClient().files.list({
1367
1548
  q: clauses.join(" and "),
1368
1549
  pageSize: maxResults || 100,
1369
1550
  fields: "files(id, name, mimeType, size, modifiedTime, webViewLink)"
@@ -1381,11 +1562,11 @@ var DriveIntegration = class extends BaseIntegration {
1381
1562
  }
1382
1563
  async getFile(params) {
1383
1564
  const fileId = params.fileId;
1384
- const meta = await this.client.files.get({
1565
+ const meta = await this.readClient().files.get({
1385
1566
  fileId,
1386
1567
  fields: "id, name, mimeType, size"
1387
1568
  });
1388
- const content = await this.client.files.get(
1569
+ const content = await this.readClient().files.get(
1389
1570
  { fileId, alt: "media" },
1390
1571
  { responseType: "arraybuffer" }
1391
1572
  );
@@ -1401,10 +1582,11 @@ var DriveIntegration = class extends BaseIntegration {
1401
1582
  async uploadFile(params) {
1402
1583
  const { name, content, mimeType, folderId } = params;
1403
1584
  const { bytes, contentType } = decodeContent(content);
1404
- const response = await this.client.files.create({
1585
+ const parent = folderId || this.defaultFolderId;
1586
+ const response = await this.writeClient().files.create({
1405
1587
  requestBody: {
1406
1588
  name,
1407
- parents: folderId ? [folderId] : void 0
1589
+ parents: parent ? [parent] : void 0
1408
1590
  },
1409
1591
  media: {
1410
1592
  mimeType: mimeType || contentType || "application/octet-stream",
@@ -1420,11 +1602,12 @@ var DriveIntegration = class extends BaseIntegration {
1420
1602
  }
1421
1603
  async createFolder(params) {
1422
1604
  const { name, parentId } = params;
1423
- const response = await this.client.files.create({
1605
+ const parent = parentId || this.defaultFolderId;
1606
+ const response = await this.writeClient().files.create({
1424
1607
  requestBody: {
1425
1608
  name,
1426
1609
  mimeType: "application/vnd.google-apps.folder",
1427
- parents: parentId ? [parentId] : void 0
1610
+ parents: parent ? [parent] : void 0
1428
1611
  },
1429
1612
  fields: "id, name"
1430
1613
  });
@@ -1432,7 +1615,7 @@ var DriveIntegration = class extends BaseIntegration {
1432
1615
  }
1433
1616
  async shareFile(params) {
1434
1617
  const { fileId, email, role } = params;
1435
- const response = await this.client.permissions.create({
1618
+ const response = await this.readClient().permissions.create({
1436
1619
  fileId,
1437
1620
  requestBody: {
1438
1621
  type: "user",
@@ -3446,6 +3629,27 @@ var PROVIDER_AUTH_URLS = {
3446
3629
  var PROVIDER_ISSUERS = {
3447
3630
  google: "https://accounts.google.com"
3448
3631
  };
3632
+ var PENDING_GRANT_TTL_MS = 10 * 60 * 1e3;
3633
+ var InMemoryPendingGrantStore = class {
3634
+ constructor() {
3635
+ this.grants = /* @__PURE__ */ new Map();
3636
+ }
3637
+ async put(state, grant, ttlMs) {
3638
+ this.grants.set(state, { grant, expiresAt: Date.now() + ttlMs });
3639
+ }
3640
+ async take(state) {
3641
+ const entry = this.grants.get(state);
3642
+ if (!entry) return null;
3643
+ this.grants.delete(state);
3644
+ return entry.expiresAt >= Date.now() ? entry.grant : null;
3645
+ }
3646
+ async sweep() {
3647
+ const now = Date.now();
3648
+ for (const [state, entry] of this.grants) {
3649
+ if (entry.expiresAt < now) this.grants.delete(state);
3650
+ }
3651
+ }
3652
+ };
3449
3653
  var OAuthIntegration = class extends BaseIntegration {
3450
3654
  constructor(config) {
3451
3655
  super(config);
@@ -3457,8 +3661,8 @@ var OAuthIntegration = class extends BaseIntegration {
3457
3661
  this.refreshIndex = /* @__PURE__ */ new Map();
3458
3662
  /** Maps access token -> mock user session */
3459
3663
  this.sessions = /* @__PURE__ */ new Map();
3460
- /** Maps state -> pending OIDC authorization (real backend) */
3461
- this.pending = /* @__PURE__ */ new Map();
3664
+ /** Pending OIDC authorizations (real backend) — injectable, in-memory default. */
3665
+ this.fallbackPending = new InMemoryPendingGrantStore();
3462
3666
  /** Maps access token -> ID-token subject, for userinfo subject checks */
3463
3667
  this.subjects = /* @__PURE__ */ new Map();
3464
3668
  /** Discovered issuer configurations, keyed by issuer URL */
@@ -3468,6 +3672,9 @@ var OAuthIntegration = class extends BaseIntegration {
3468
3672
  this.real ? "OAuth integration initialized (OIDC backend via openid-client)" : "OAuth integration initialized (mock backend)"
3469
3673
  );
3470
3674
  }
3675
+ pendingStore() {
3676
+ return this.fallbackPending;
3677
+ }
3471
3678
  async execute(action, params) {
3472
3679
  const validation = this.validateParams(action, params);
3473
3680
  if (!validation.valid) {
@@ -3559,17 +3766,17 @@ var OAuthIntegration = class extends BaseIntegration {
3559
3766
  parameters.prompt = "consent";
3560
3767
  }
3561
3768
  const authUrl = oidc.buildAuthorizationUrl(configuration, parameters);
3562
- this.pending.set(state, { provider, redirectUri, pkceVerifier });
3769
+ await this.pendingStore().put(state, { provider, redirectUri, pkceVerifier }, PENDING_GRANT_TTL_MS);
3770
+ void this.pendingStore().sweep();
3563
3771
  return { authUrl: authUrl.toString(), state };
3564
3772
  }
3565
3773
  async oidcToken(params) {
3566
3774
  const code = params.code;
3567
3775
  const state = params.state;
3568
- const pendingAuth = this.pending.get(state);
3776
+ const pendingAuth = await this.pendingStore().take(state);
3569
3777
  if (!pendingAuth) {
3570
3778
  throw new Error(`Invalid or expired state token: ${state}`);
3571
3779
  }
3572
- this.pending.delete(state);
3573
3780
  const configuration = await this.configurationFor(pendingAuth.provider);
3574
3781
  const callbackUrl = new URL(pendingAuth.redirectUri);
3575
3782
  callbackUrl.searchParams.set("code", code);
@@ -3756,131 +3963,10 @@ var OAuthIntegration = class extends BaseIntegration {
3756
3963
  };
3757
3964
  registerIntegration("oauth", OAuthIntegration);
3758
3965
 
3759
- // src/contracts.ts
3760
- var serviceCredentials = {
3761
- stripe: [
3762
- { envVar: "STRIPE_SECRET_KEY", required: true, description: "Stripe secret API key" },
3763
- { envVar: "STRIPE_WEBHOOK_SECRET", required: false, description: "Webhook endpoint signing secret" }
3764
- ],
3765
- youtube: [
3766
- { envVar: "YOUTUBE_API_KEY", required: true, description: "YouTube Data API key" }
3767
- ],
3768
- twilio: [
3769
- { envVar: "TWILIO_ACCOUNT_SID", required: true, description: "Twilio account SID" },
3770
- { envVar: "TWILIO_AUTH_TOKEN", required: true, description: "Twilio auth token" },
3771
- { envVar: "TWILIO_PHONE_NUMBER", required: false, description: "Default sender phone number" }
3772
- ],
3773
- email: [
3774
- { envVar: "SENDGRID_API_KEY", required: false, description: "SendGrid API key (use this OR RESEND_API_KEY)" },
3775
- { envVar: "RESEND_API_KEY", required: false, description: "Resend API key (use this OR SENDGRID_API_KEY)" },
3776
- { envVar: "FROM_EMAIL", required: false, description: "Default sender email address" }
3777
- ],
3778
- webhook: [
3779
- { envVar: "WEBHOOK_SIGNING_SECRET", required: false, description: "Default HMAC-SHA256 signing secret (per-call secret overrides)" },
3780
- { envVar: "WEBHOOK_TIMEOUT_MS", required: false, description: "Per-request timeout (ms, default 10000)" }
3781
- ],
3782
- push: [
3783
- { envVar: "VAPID_PUBLIC_KEY", required: true, description: "VAPID public key (web-push generate-vapid-keys)" },
3784
- { envVar: "VAPID_PRIVATE_KEY", required: true, description: "VAPID private key" },
3785
- { envVar: "VAPID_SUBJECT", required: true, description: "VAPID subject (mailto: or https: contact URI)" }
3786
- ],
3787
- calendar: [
3788
- { envVar: "GOOGLE_CALENDAR_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with calendar scope; store in Secret Manager, bind as env" },
3789
- { envVar: "GOOGLE_CALENDAR_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
3790
- { envVar: "GOOGLE_CALENDAR_ID", required: false, description: "Default calendar id (default: primary)" }
3791
- ],
3792
- drive: [
3793
- { envVar: "GOOGLE_DRIVE_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with drive scope; store in Secret Manager, bind as env" },
3794
- { envVar: "GOOGLE_DRIVE_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" }
3795
- ],
3796
- metaAds: [
3797
- { envVar: "META_ACCESS_TOKEN", required: true, description: "Meta Graph API access token (Marketing API, ads_read)" },
3798
- { envVar: "META_AD_ACCOUNT_ID", required: false, description: "Default ad account id (act_\u2026); per-call accountId overrides" }
3799
- ],
3800
- accounting: [],
3801
- banking: [
3802
- { envVar: "GOCARDLESS_SECRET_ID", required: true, description: "GoCardless Bank Account Data secret id" },
3803
- { envVar: "GOCARDLESS_SECRET_KEY", required: true, description: "GoCardless Bank Account Data secret key" }
3804
- ],
3805
- esign: [
3806
- { envVar: "DOCUSIGN_BASE_URL", required: true, description: "DocuSign REST base (e.g. https://demo.docusign.net/restapi/v2.1/accounts/<accountId>)" },
3807
- { envVar: "DOCUSIGN_ACCESS_TOKEN", required: true, description: "DocuSign OAuth access token (JWT grant rotation is the deployment concern)" }
3808
- ],
3809
- llm: [
3810
- { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key (use this OR OPENAI_API_KEY)" },
3811
- { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key (use this OR ANTHROPIC_API_KEY)" }
3812
- ],
3813
- "llm-integration": [
3814
- { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key" },
3815
- { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key" }
3816
- ],
3817
- ml: [
3818
- { envVar: "MASAR_URL", required: true, description: "Base URL of the deployed model-serving orbital" },
3819
- { envVar: "MASAR_ML_TRAIT", required: true, description: "Kebab-case trait name the serving orbital exposes its /events route under" }
3820
- ],
3821
- deepagent: [
3822
- { envVar: "DEEPAGENT_API_URL", required: true, description: "DeepAgent server URL" },
3823
- { envVar: "DEEPAGENT_API_KEY", required: false, description: "DeepAgent API key" }
3824
- ],
3825
- github: [
3826
- { envVar: "GITHUB_TOKEN", required: true, description: "GitHub personal access token" },
3827
- { envVar: "GITHUB_OWNER", required: false, description: "Default repository owner" },
3828
- { envVar: "GITHUB_REPO", required: false, description: "Default repository name" }
3829
- ],
3830
- docker: [
3831
- { envVar: "DOCKER_HOST", required: false, description: "Docker daemon host URL" }
3832
- ],
3833
- storage: [
3834
- { envVar: "STORAGE_ACCESS_KEY_ID", required: true, description: "S3-compatible access key id" },
3835
- { envVar: "STORAGE_SECRET_ACCESS_KEY", required: true, description: "S3-compatible secret access key" },
3836
- { envVar: "STORAGE_BUCKET", required: true, description: "Default bucket name" },
3837
- { envVar: "STORAGE_REGION", required: false, description: "Region (default us-east-1)" },
3838
- { envVar: "STORAGE_ENDPOINT", required: false, description: "Custom S3-compatible endpoint (R2/MinIO/\u2026); empty = AWS S3" },
3839
- { envVar: "STORAGE_PUBLIC_URL_BASE", required: false, description: "Base URL for public-acl object links; empty = endpoint-derived" }
3840
- ],
3841
- queue: [
3842
- { envVar: "QUEUE_URL", required: true, description: "Message queue connection URL" }
3843
- ],
3844
- redis: [
3845
- { envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
3846
- ],
3847
- oauth: [
3848
- { envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
3849
- { envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
3850
- { envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
3851
- { envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
3852
- ],
3853
- credentials: [
3854
- { envVar: "ALMADAR_CREDENTIAL_MASTER_KEY", required: false, description: "AES-256 master key (64-char hex) enabling the hosted credential store \u2014 hold it alone in the platform secret store" }
3855
- ],
3856
- otel: [
3857
- { envVar: "OTEL_EXPORTER_OTLP_ENDPOINT", required: true, description: "OpenTelemetry collector endpoint" },
3858
- { envVar: "OTEL_SERVICE_NAME", required: false, description: "Service name for traces" }
3859
- ],
3860
- cli: [],
3861
- // No fixed env vars — connection strings are resolved per-query from the
3862
- // caller-supplied connectionRef, so credentials cannot be declared statically.
3863
- database: [],
3864
- wikimedia: [
3865
- { envVar: "WIKIMEDIA_USER_AGENT", required: false, description: "Descriptive User-Agent for the Wikipedia API" },
3866
- { envVar: "WIKIMEDIA_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
3867
- ],
3868
- iconify: [
3869
- { envVar: "ICONIFY_USER_AGENT", required: false, description: "User-Agent for the Iconify API" },
3870
- { envVar: "ICONIFY_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
3871
- ],
3872
- arxiv: [
3873
- { envVar: "ARXIV_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
3874
- ]
3875
- };
3876
- var serviceProbes = {
3877
- calendar: { action: "listEvents", params: { maxResults: 1 } },
3878
- drive: { action: "listFiles", params: { maxResults: 1 } },
3879
- metaAds: { action: "listCampaigns", params: {} }
3880
- };
3881
-
3882
3966
  // src/integrations/credentials/index.ts
3883
3967
  var ENV_VAR_NAME = /^[A-Z][A-Z0-9_]*$/;
3968
+ var ROLE_GATED_ACTIONS = /* @__PURE__ */ new Set(["set", "remove", "test", "rotate"]);
3969
+ var DEFAULT_ADMIN_ROLES = ["admin", "owner"];
3884
3970
  function isProbeService(service) {
3885
3971
  return service in serviceProbes;
3886
3972
  }
@@ -3889,7 +3975,20 @@ var CredentialsIntegration = class extends BaseIntegration {
3889
3975
  super(config);
3890
3976
  this.logger.info("Credentials integration initialized (tenant credential store surface)");
3891
3977
  }
3892
- async execute(action, params) {
3978
+ async execute(action, params, context) {
3979
+ if (ROLE_GATED_ACTIONS.has(action)) {
3980
+ const allowed = this.adminRoles();
3981
+ if (!context?.role || !allowed.includes(context.role)) {
3982
+ return {
3983
+ success: false,
3984
+ error: new IntegrationError(
3985
+ `forbidden: "${action}" requires one of roles: ${allowed.join(", ")}`,
3986
+ "AUTH_ERROR"
3987
+ ),
3988
+ metadata: this.createMetadata(action, 0)
3989
+ };
3990
+ }
3991
+ }
3893
3992
  const validation = this.validateParams(action, params);
3894
3993
  if (!validation.valid) {
3895
3994
  return {
@@ -3917,8 +4016,16 @@ var CredentialsIntegration = class extends BaseIntegration {
3917
4016
  data = await this.remove(params.service, params.envVar);
3918
4017
  break;
3919
4018
  case "test":
3920
- data = await this.test(params.service);
4019
+ data = await this.test(params.service, context);
3921
4020
  break;
4021
+ case "rotate": {
4022
+ const store = getInstalledCredentialStore();
4023
+ if (!store) {
4024
+ throw new Error("No credential store is installed on this host");
4025
+ }
4026
+ data = await store.rotate();
4027
+ break;
4028
+ }
3922
4029
  default:
3923
4030
  throw new Error(`Unknown action: ${action}`);
3924
4031
  }
@@ -3931,6 +4038,11 @@ var CredentialsIntegration = class extends BaseIntegration {
3931
4038
  return this.handleError(action, error);
3932
4039
  }
3933
4040
  }
4041
+ adminRoles() {
4042
+ const raw = this.config.env["ALMADAR_CREDENTIAL_ADMIN_ROLES"] || process.env["ALMADAR_CREDENTIAL_ADMIN_ROLES"] || "";
4043
+ const roles = raw.split(",").map((r) => r.trim()).filter((r) => r.length > 0);
4044
+ return roles.length > 0 ? roles : DEFAULT_ADMIN_ROLES;
4045
+ }
3934
4046
  declaredFor(service) {
3935
4047
  return serviceCredentials[service] ?? [];
3936
4048
  }
@@ -3950,6 +4062,10 @@ var CredentialsIntegration = class extends BaseIntegration {
3950
4062
  throw new Error(`"${envVar}" is not a declared credential of "${service}" (declared: ${valid})`);
3951
4063
  }
3952
4064
  }
4065
+ storeFirst() {
4066
+ const flag = this.config.env["ALMADAR_CREDENTIALS_SOURCE"] || process.env["ALMADAR_CREDENTIALS_SOURCE"];
4067
+ return flag === "store";
4068
+ }
3953
4069
  list(serviceFilter) {
3954
4070
  const store = getInstalledCredentialStore();
3955
4071
  const stored = new Map((store?.entries() ?? []).map((e) => [`${e.service}\0${e.envVar}`, e]));
@@ -3959,7 +4075,7 @@ var CredentialsIntegration = class extends BaseIntegration {
3959
4075
  for (const { envVar, required, description } of declared) {
3960
4076
  const fromStore = stored.get(`${service}\0${envVar}`);
3961
4077
  stored.delete(`${service}\0${envVar}`);
3962
- const fromEnv = process.env[envVar];
4078
+ const fromEnv = this.storeFirst() ? void 0 : process.env[envVar];
3963
4079
  const source = fromStore ? "store" : fromEnv ? "env" : "none";
3964
4080
  entries.push({
3965
4081
  service,
@@ -4003,7 +4119,7 @@ var CredentialsIntegration = class extends BaseIntegration {
4003
4119
  this.assertSettable(service, envVar);
4004
4120
  return { removed: await store.remove(envVar) };
4005
4121
  }
4006
- async test(service) {
4122
+ async test(service, context) {
4007
4123
  const declared = this.declaredFor(service);
4008
4124
  const missing = declared.filter((c) => c.required && !resolveCredentialRef(c.envVar)).map((c) => c.envVar);
4009
4125
  const configured = missing.length === 0;
@@ -4018,7 +4134,7 @@ var CredentialsIntegration = class extends BaseIntegration {
4018
4134
  if (!factory.isConfigured(service)) {
4019
4135
  return { service, configured, missing, probed: false, ok: false, message: "Credentials present but the service is not configured on this host \u2014 restart or re-save a credential" };
4020
4136
  }
4021
- const result = await factory.execute(service, probe.action, probe.params);
4137
+ const result = await factory.execute(service, probe.action, probe.params, context);
4022
4138
  if (!result.success) {
4023
4139
  return { service, configured, missing, probed: true, ok: false, message: result.error?.message ?? `Probe ${probe.action} failed` };
4024
4140
  }
@@ -4248,10 +4364,16 @@ var StorageIntegration = class extends BaseIntegration {
4248
4364
  const bucket = this.bucketOf(params);
4249
4365
  const prefix = params.prefix ?? "";
4250
4366
  const maxKeys = params.maxKeys ?? 1e3;
4367
+ const continuationToken = params.continuationToken;
4251
4368
  this.logger.debug("Storage LIST", { bucket, prefix, maxKeys });
4252
4369
  if (this.s3) {
4253
4370
  const response = await this.s3.send(
4254
- new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix || void 0, MaxKeys: maxKeys })
4371
+ new ListObjectsV2Command({
4372
+ Bucket: bucket,
4373
+ Prefix: prefix || void 0,
4374
+ MaxKeys: maxKeys,
4375
+ ContinuationToken: continuationToken
4376
+ })
4255
4377
  );
4256
4378
  return {
4257
4379
  keys: (response.Contents ?? []).map((entry) => ({
@@ -4259,9 +4381,8 @@ var StorageIntegration = class extends BaseIntegration {
4259
4381
  size: entry.Size ?? 0,
4260
4382
  lastModified: entry.LastModified?.getTime() ?? 0
4261
4383
  })),
4262
- // Continuation tokens are unrepresentable in the result type (ledger
4263
- // I-11) surface the clamp honestly.
4264
- truncated: Boolean(response.IsTruncated)
4384
+ truncated: Boolean(response.IsTruncated),
4385
+ ...response.IsTruncated && response.NextContinuationToken !== void 0 ? { nextToken: response.NextContinuationToken } : {}
4265
4386
  };
4266
4387
  }
4267
4388
  const bucketPrefix = `${bucket}/`;
@@ -4276,7 +4397,14 @@ var StorageIntegration = class extends BaseIntegration {
4276
4397
  });
4277
4398
  }
4278
4399
  results.sort((a, b) => a.key.localeCompare(b.key));
4279
- return { keys: results.slice(0, maxKeys), truncated: results.length > maxKeys };
4400
+ const offset = continuationToken !== void 0 ? Number.parseInt(continuationToken, 10) || 0 : 0;
4401
+ const page = results.slice(offset, offset + maxKeys);
4402
+ const truncated = offset + maxKeys < results.length;
4403
+ return {
4404
+ keys: page,
4405
+ truncated,
4406
+ ...truncated ? { nextToken: String(offset + maxKeys) } : {}
4407
+ };
4280
4408
  }
4281
4409
  async deleteObject(params) {
4282
4410
  const bucket = this.bucketOf(params);
@@ -5128,6 +5256,19 @@ function createCallServiceHandler(factory) {
5128
5256
  }
5129
5257
 
5130
5258
  // src/runtime/RuntimeIntegrationManager.ts
5259
+ var STORE_BOOTSTRAP_ENV_VARS = /* @__PURE__ */ new Set([
5260
+ "ALMADAR_CREDENTIAL_MASTER_KEY",
5261
+ "ALMADAR_CREDENTIAL_MASTER_KEY_PREVIOUS"
5262
+ ]);
5263
+ function declaredCredentialEnvVars() {
5264
+ const vars = /* @__PURE__ */ new Set();
5265
+ for (const decls of Object.values(serviceCredentials)) {
5266
+ for (const { envVar } of decls) {
5267
+ if (!STORE_BOOTSTRAP_ENV_VARS.has(envVar)) vars.add(envVar);
5268
+ }
5269
+ }
5270
+ return vars;
5271
+ }
5131
5272
  function generateMockFromShape(shape) {
5132
5273
  const data = {};
5133
5274
  for (const [key, type] of Object.entries(shape)) {
@@ -5157,6 +5298,7 @@ var RuntimeIntegrationManager = class {
5157
5298
  constructor() {
5158
5299
  this.credentialStore = null;
5159
5300
  this.storeEnvBase = null;
5301
+ this.storeFirst = false;
5160
5302
  this.factory = new IntegrationFactory();
5161
5303
  this.installNotConfiguredFallback();
5162
5304
  installActiveFactory(this.factory);
@@ -5172,16 +5314,51 @@ var RuntimeIntegrationManager = class {
5172
5314
  async configureFromStore(store, envOverride) {
5173
5315
  this.credentialStore = store;
5174
5316
  this.storeEnvBase = envOverride ?? process.env;
5317
+ this.storeFirst = this.storeEnvBase.ALMADAR_CREDENTIALS_SOURCE === "store";
5175
5318
  installCredentialStore(store);
5176
5319
  await store.warm();
5320
+ if (this.storeFirst && store.enabled) {
5321
+ await this.seedStoreFromEnv(store, this.storeEnvBase);
5322
+ }
5177
5323
  store.subscribe(() => this.refreshFromStore());
5178
5324
  this.refreshFromStore();
5179
5325
  }
5180
- /** Re-derive configs from base env + warmed store values; drop stale instances. */
5326
+ /** Whether store-first custody (I-31) is active on this manager. */
5327
+ get credentialsStoreFirst() {
5328
+ return this.storeFirst;
5329
+ }
5330
+ /** Copy each declared credential present in env but absent from the store. */
5331
+ async seedStoreFromEnv(store, env) {
5332
+ const present = new Set(store.entries().map((e) => e.envVar));
5333
+ for (const [service, decls] of Object.entries(serviceCredentials)) {
5334
+ for (const { envVar } of decls) {
5335
+ if (STORE_BOOTSTRAP_ENV_VARS.has(envVar)) continue;
5336
+ const value = env[envVar];
5337
+ if (value && !present.has(envVar)) {
5338
+ await store.set(service, envVar, value);
5339
+ present.add(envVar);
5340
+ }
5341
+ }
5342
+ }
5343
+ }
5344
+ /**
5345
+ * Re-derive configs from base env + warmed store values. `reset()` (not
5346
+ * `invalidate()`): configs must go too, or a REMOVED credential's service
5347
+ * resurrects from its stale config on the next `get()` — removal must
5348
+ * revoke. `configureFromEnv` below rebuilds every configured service from
5349
+ * the merged env.
5350
+ */
5181
5351
  refreshFromStore() {
5182
5352
  if (!this.credentialStore || !this.storeEnvBase) return;
5183
- this.factory.invalidate();
5184
- this.configureFromEnv({ ...this.storeEnvBase, ...this.credentialStore.snapshotEnv() });
5353
+ this.factory.reset();
5354
+ let base = this.storeEnvBase;
5355
+ if (this.storeFirst) {
5356
+ const declared = declaredCredentialEnvVars();
5357
+ base = Object.fromEntries(
5358
+ Object.entries(this.storeEnvBase).filter(([key]) => !declared.has(key))
5359
+ );
5360
+ }
5361
+ this.configureFromEnv({ ...base, ...this.credentialStore.snapshotEnv() });
5185
5362
  }
5186
5363
  /**
5187
5364
  * Wrap `factory.execute` so an unknown/unconfigured service echoes its
@@ -5290,11 +5467,15 @@ var RuntimeIntegrationManager = class {
5290
5467
  }
5291
5468
  });
5292
5469
  }
5293
- if (env.GOOGLE_DRIVE_SA_KEY) {
5470
+ if (env.GOOGLE_DRIVE_SA_KEY || env.GOOGLE_DRIVE_REFRESH_TOKEN && env.OAUTH_CLIENT_ID && env.OAUTH_CLIENT_SECRET) {
5294
5471
  this.factory.configure("drive", {
5295
5472
  env: {
5296
- GOOGLE_DRIVE_SA_KEY: env.GOOGLE_DRIVE_SA_KEY,
5297
- GOOGLE_DRIVE_SUBJECT: env.GOOGLE_DRIVE_SUBJECT || ""
5473
+ GOOGLE_DRIVE_SA_KEY: env.GOOGLE_DRIVE_SA_KEY || "",
5474
+ GOOGLE_DRIVE_SUBJECT: env.GOOGLE_DRIVE_SUBJECT || "",
5475
+ GOOGLE_DRIVE_REFRESH_TOKEN: env.GOOGLE_DRIVE_REFRESH_TOKEN || "",
5476
+ GOOGLE_DRIVE_FOLDER_ID: env.GOOGLE_DRIVE_FOLDER_ID || "",
5477
+ OAUTH_CLIENT_ID: env.OAUTH_CLIENT_ID || "",
5478
+ OAUTH_CLIENT_SECRET: env.OAUTH_CLIENT_SECRET || ""
5298
5479
  }
5299
5480
  });
5300
5481
  }
@@ -5334,7 +5515,12 @@ var RuntimeIntegrationManager = class {
5334
5515
  }
5335
5516
  });
5336
5517
  }
5337
- this.factory.configure("credentials", { env: {} });
5518
+ this.factory.configure("credentials", {
5519
+ env: {
5520
+ ALMADAR_CREDENTIAL_ADMIN_ROLES: env.ALMADAR_CREDENTIAL_ADMIN_ROLES || "",
5521
+ ALMADAR_CREDENTIALS_SOURCE: env.ALMADAR_CREDENTIALS_SOURCE || ""
5522
+ }
5523
+ });
5338
5524
  this.factory.configure("storage", {
5339
5525
  env: {
5340
5526
  STORAGE_ACCESS_KEY_ID: env.STORAGE_ACCESS_KEY_ID || "",