@opengeni/config 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/config",
3
- "version": "1.2.2",
3
+ "version": "2.0.0",
4
4
  "description": "OpenGeni runtime configuration: settings resolution, deployment knobs, and config validation shared across the server packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
34
34
  },
35
35
  "dependencies": {
36
- "@opengeni/codex": "^0.2.23",
37
- "@opengeni/contracts": "^4.1.0",
36
+ "@opengeni/codex": "^0.2.24",
37
+ "@opengeni/contracts": "^5.0.0",
38
38
  "@opengeni/xai-subscription": "^0.1.4",
39
39
  "zod": "^4.2.1"
40
40
  },
package/src/index.ts CHANGED
@@ -197,7 +197,7 @@ export const AGENT_INSTRUCTIONS_CORE_PLACEHOLDER = "{{core}}";
197
197
  */
198
198
  export const DEFAULT_AGENT_INSTRUCTIONS = [
199
199
  "You are an OpenGeni workspace agent.",
200
- "Follow the user's task and any enabled pack or skill instructions for the current role.",
200
+ "Follow the user's task and the applicable Skill instructions for the current role.",
201
201
  "Work inside the sandbox workspace and use filesystem and shell tools when useful.",
202
202
  "Repository resources are mounted under repos/<host>/<owner>/<repo> unless the session specifies another collision-free mount path.",
203
203
  "File resources are mounted under .opengeni/files/<file-id>/ unless the session specifies another mount path.",
@@ -285,6 +285,8 @@ const SettingsSchema = z.object({
285
285
  observabilityMetricsEnabled: EnvBoolean.default(true),
286
286
  observabilityOtlpEndpoint: z.string().url().optional(),
287
287
  observabilityOtlpHeaders: z.string().default(""),
288
+ observabilityDiagnosticsEndpoint: z.string().url().optional(),
289
+ observabilityDiagnosticsHeaders: z.string().default(""),
288
290
  analyticsEnabled: EnvBoolean.default(false),
289
291
  analyticsConsentRequired: EnvBoolean.default(true),
290
292
  analyticsReoClientId: z
@@ -484,16 +486,6 @@ const SettingsSchema = z.object({
484
486
  // into @opengeni/db once at boot.
485
487
  // Env: OPENGENI_CHILD_LIFECYCLE_NOTICES_ENABLED.
486
488
  childLifecycleNoticesEnabled: EnvBoolean.default(false),
487
- // Explicit host-owned MCP connection authority is a rolling protocol
488
- // activation. Keep it off while any API, worker, or browser bundle predates
489
- // the authority discriminator; enable it only after the whole fleet runs an
490
- // image that understands host refs. Legacy markerless non-UUID refs remain a
491
- // separate compatibility lane for already-persisted embedding integrations.
492
- // Env: OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED.
493
- hostMcpAuthoritySourceAdmissionEnabled: EnvBoolean.default(false),
494
- // Server-owned per-organization remote MCP resolver configuration. Optional;
495
- // inline credentials and native provider OAuth do not require this service.
496
- hostMcpCredentialResolversJson: z.string().optional(),
497
489
  // Per-channel and per-DM Slack workspace routing. Default ON. A channel does
498
490
  // not count a personal workspace as a candidate, so an organization with one
499
491
  // shared workspace resolves it as the sole candidate and never asks; the
@@ -744,6 +736,7 @@ const SettingsSchema = z.object({
744
736
  // rotated TO — when EITHER usage window (5h/weekly) is at/over this percent. Default 90 to
745
737
  // match the UI danger flip (UsageBar danger at pct >= 90). OPENGENI_CODEX_ROTATION_NEAR_EXHAUSTION_PCT.
746
738
  codexRotationNearExhaustionPct: z.coerce.number().int().min(1).max(100).default(90),
739
+ reasoningConfigurationUpdatesEnabled: EnvBoolean.default(false),
747
740
  openaiReasoningEffort: ReasoningEffort.default("low"),
748
741
  openaiAllowedReasoningEfforts: z.string().default("low,medium,high,xhigh,max"),
749
742
  openaiResponsesTransport: z.enum(["http", "websocket"]).default("http"),
@@ -792,7 +785,7 @@ const SettingsSchema = z.object({
792
785
  // the verified, self-contained native artifact runtime at its fixed image
793
786
  // paths. Disabled by default so arbitrary/custom provider images never make
794
787
  // document/spreadsheet/presentation skills appear when their runtime is
795
- // absent. Per-pack/per-rig image overrides fail closed in the worker even
788
+
796
789
  // when this base-image contract is enabled.
797
790
  sandboxArtifactRuntimeEnabled: EnvBoolean.default(false),
798
791
  dockerExposedPorts: z.string().default(""),
@@ -804,6 +797,9 @@ const SettingsSchema = z.object({
804
797
  dockerWorkspaceBaseDir: z.string().min(1).optional(),
805
798
  modalAppName: z.string().default("opengeni-sandbox"),
806
799
  modalImageRef: z.string().optional(),
800
+ // Activate only after descriptor-aware readers, DB fences and the exact
801
+ // native image have passed the supervised-command rollout canary.
802
+ modalCommandSupervisionEnabled: EnvBoolean.default(false),
807
803
  // Provider-native immutable Modal image ID for the exact logical
808
804
  // `modalImageRef`. When set, the runtime uses ModalImageSelector.fromId and
809
805
  // never asks Modal to parse or import the registry ref. The logical ref is
@@ -1332,6 +1328,124 @@ const SettingsSchema = z.object({
1332
1328
  });
1333
1329
 
1334
1330
  export type Settings = z.infer<typeof SettingsSchema>;
1331
+
1332
+ // Independently supervised artifact services are not API/agent runtimes. Parse
1333
+ // only their actual capabilities, using the same field schemas as getSettings.
1334
+ const ArtifactServiceSettingsSchema = SettingsSchema.pick({
1335
+ serviceName: true,
1336
+ environment: true,
1337
+ deploymentRevision: true,
1338
+ dbSchema: true,
1339
+ rlsStrategy: true,
1340
+ observabilityStructuredLogs: true,
1341
+ observabilityMetricsEnabled: true,
1342
+ observabilityOtlpEndpoint: true,
1343
+ observabilityOtlpHeaders: true,
1344
+ });
1345
+ const ArtifactOutboxSettingsSchema = ArtifactServiceSettingsSchema.extend({
1346
+ natsUrl: SettingsSchema.shape.natsUrl,
1347
+ selfhostedNatsControlUser: SettingsSchema.shape.selfhostedNatsControlUser,
1348
+ selfhostedNatsControlPassword: SettingsSchema.shape.selfhostedNatsControlPassword,
1349
+ });
1350
+ const ObjectStorageSettingsSchema = SettingsSchema.pick({
1351
+ objectStorageBackend: true,
1352
+ objectStorageEndpoint: true,
1353
+ objectStorageInternalEndpoint: true,
1354
+ objectStorageSandboxEndpoint: true,
1355
+ objectStorageBucket: true,
1356
+ objectStorageRegion: true,
1357
+ objectStorageAccessKeyId: true,
1358
+ objectStorageSecretAccessKey: true,
1359
+ objectStorageForcePathStyle: true,
1360
+ objectStorageAzureConnectionString: true,
1361
+ objectStorageAzureAccountName: true,
1362
+ objectStorageAzureAccountKey: true,
1363
+ objectStorageAzureEndpoint: true,
1364
+ objectStorageGcsProjectId: true,
1365
+ objectStorageGcsCredentialsJson: true,
1366
+ objectStorageGcsKeyFilename: true,
1367
+ objectStorageGcsApiEndpoint: true,
1368
+ });
1369
+ const ArtifactMaterializerSettingsSchema = ArtifactServiceSettingsSchema.extend(
1370
+ ObjectStorageSettingsSchema.shape,
1371
+ );
1372
+ export type ArtifactOutboxSettings = z.infer<typeof ArtifactOutboxSettingsSchema>;
1373
+ export type ObjectStorageSettings = z.infer<typeof ObjectStorageSettingsSchema>;
1374
+ export type ArtifactMaterializerSettings = z.infer<typeof ArtifactMaterializerSettingsSchema>;
1375
+
1376
+ function artifactServiceEnvironment(source: NodeJS.ProcessEnv) {
1377
+ const optional = (name: string) => optionalEnvironmentValue(name, source);
1378
+ return {
1379
+ serviceName: optional("OPENGENI_SERVICE_NAME"),
1380
+ environment: optional("OPENGENI_ENVIRONMENT"),
1381
+ deploymentRevision:
1382
+ optional("OPENGENI_DEPLOYMENT_REVISION") ??
1383
+ optional("SOURCE_VERSION") ??
1384
+ optional("GITHUB_SHA"),
1385
+ dbSchema: optional("OPENGENI_DB_SCHEMA"),
1386
+ rlsStrategy: optional("OPENGENI_RLS_STRATEGY"),
1387
+ observabilityStructuredLogs: optional("OPENGENI_OBSERVABILITY_STRUCTURED_LOGS"),
1388
+ observabilityMetricsEnabled: optional("OPENGENI_OBSERVABILITY_METRICS_ENABLED"),
1389
+ observabilityOtlpEndpoint:
1390
+ optional("OPENGENI_OTEL_EXPORTER_OTLP_ENDPOINT") ?? optional("OTEL_EXPORTER_OTLP_ENDPOINT"),
1391
+ observabilityOtlpHeaders:
1392
+ optional("OPENGENI_OTEL_EXPORTER_OTLP_HEADERS") ?? optional("OTEL_EXPORTER_OTLP_HEADERS"),
1393
+ };
1394
+ }
1395
+
1396
+ export function getArtifactOutboxSettings(
1397
+ source: NodeJS.ProcessEnv = process.env,
1398
+ ): ArtifactOutboxSettings {
1399
+ const settings = ArtifactOutboxSettingsSchema.parse({
1400
+ ...artifactServiceEnvironment(source),
1401
+ natsUrl: optionalEnvironmentValue("OPENGENI_NATS_URL", source),
1402
+ selfhostedNatsControlUser: optionalEnvironmentValue(
1403
+ "OPENGENI_SELFHOSTED_NATS_CONTROL_USER",
1404
+ source,
1405
+ ),
1406
+ selfhostedNatsControlPassword: optionalEnvironmentValue(
1407
+ "OPENGENI_SELFHOSTED_NATS_CONTROL_PASSWORD",
1408
+ source,
1409
+ ),
1410
+ });
1411
+ dbSearchPath(settings);
1412
+ if (
1413
+ Boolean(settings.selfhostedNatsControlUser) !== Boolean(settings.selfhostedNatsControlPassword)
1414
+ ) {
1415
+ throw new Error("Artifact outbox NATS control user and password must be configured together");
1416
+ }
1417
+ return settings;
1418
+ }
1419
+
1420
+ export function getArtifactMaterializerSettings(
1421
+ source: NodeJS.ProcessEnv = process.env,
1422
+ ): ArtifactMaterializerSettings {
1423
+ const optional = (name: string) => optionalEnvironmentValue(name, source);
1424
+ const settings = ArtifactMaterializerSettingsSchema.parse({
1425
+ ...artifactServiceEnvironment(source),
1426
+ objectStorageEndpoint: optional("OPENGENI_OBJECT_STORAGE_ENDPOINT"),
1427
+ objectStorageInternalEndpoint: optional("OPENGENI_OBJECT_STORAGE_INTERNAL_ENDPOINT"),
1428
+ objectStorageSandboxEndpoint: optional("OPENGENI_OBJECT_STORAGE_SANDBOX_ENDPOINT"),
1429
+ objectStorageBackend: optional("OPENGENI_OBJECT_STORAGE_BACKEND"),
1430
+ objectStorageBucket: optional("OPENGENI_OBJECT_STORAGE_BUCKET"),
1431
+ objectStorageRegion: optional("OPENGENI_OBJECT_STORAGE_REGION"),
1432
+ objectStorageAccessKeyId: optional("OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID"),
1433
+ objectStorageSecretAccessKey: optional("OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY"),
1434
+ objectStorageForcePathStyle: optional("OPENGENI_OBJECT_STORAGE_FORCE_PATH_STYLE"),
1435
+ objectStorageAzureConnectionString: optional("OPENGENI_OBJECT_STORAGE_AZURE_CONNECTION_STRING"),
1436
+ objectStorageAzureAccountName: optional("OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_NAME"),
1437
+ objectStorageAzureAccountKey: optional("OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_KEY"),
1438
+ objectStorageAzureEndpoint: optional("OPENGENI_OBJECT_STORAGE_AZURE_ENDPOINT"),
1439
+ objectStorageGcsProjectId: optional("OPENGENI_OBJECT_STORAGE_GCS_PROJECT_ID"),
1440
+ objectStorageGcsCredentialsJson: optional("OPENGENI_OBJECT_STORAGE_GCS_CREDENTIALS_JSON"),
1441
+ objectStorageGcsKeyFilename: optional("OPENGENI_OBJECT_STORAGE_GCS_KEY_FILENAME"),
1442
+ objectStorageGcsApiEndpoint: optional("OPENGENI_OBJECT_STORAGE_GCS_API_ENDPOINT"),
1443
+ });
1444
+ dbSearchPath(settings);
1445
+ validateObjectStorageSettings(settings);
1446
+ return settings;
1447
+ }
1448
+
1335
1449
  export type McpServerConfig = Settings["mcpServers"][number];
1336
1450
 
1337
1451
  export type GoogleDriveProviderRetryOptions = {
@@ -2826,6 +2940,8 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
2826
2940
  optional("OPENGENI_OTEL_EXPORTER_OTLP_ENDPOINT") ?? optional("OTEL_EXPORTER_OTLP_ENDPOINT"),
2827
2941
  observabilityOtlpHeaders:
2828
2942
  optional("OPENGENI_OTEL_EXPORTER_OTLP_HEADERS") ?? optional("OTEL_EXPORTER_OTLP_HEADERS"),
2943
+ observabilityDiagnosticsEndpoint: optional("OPENGENI_OBSERVABILITY_DIAGNOSTICS_ENDPOINT"),
2944
+ observabilityDiagnosticsHeaders: optional("OPENGENI_OBSERVABILITY_DIAGNOSTICS_HEADERS"),
2829
2945
  analyticsEnabled: optional("OPENGENI_ANALYTICS_ENABLED"),
2830
2946
  analyticsConsentRequired: optional("OPENGENI_ANALYTICS_CONSENT_REQUIRED"),
2831
2947
  analyticsReoClientId: optional("OPENGENI_ANALYTICS_REO_CLIENT_ID"),
@@ -2906,10 +3022,6 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
2906
3022
  goalIdleBackoffMs: optional("OPENGENI_GOAL_IDLE_BACKOFF_MS"),
2907
3023
  goalIdleBackoffMaxMs: optional("OPENGENI_GOAL_IDLE_BACKOFF_MAX_MS"),
2908
3024
  childLifecycleNoticesEnabled: optional("OPENGENI_CHILD_LIFECYCLE_NOTICES_ENABLED"),
2909
- hostMcpAuthoritySourceAdmissionEnabled: optional(
2910
- "OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED",
2911
- ),
2912
- hostMcpCredentialResolversJson: optional("OPENGENI_HOST_MCP_CREDENTIAL_RESOLVERS_JSON"),
2913
3025
  slackWorkspaceRoutingEnabled: optional("OPENGENI_SLACK_WORKSPACE_ROUTING_ENABLED"),
2914
3026
  agentMaxModelCallsPerTurn: optional("OPENGENI_AGENT_MAX_MODEL_CALLS_PER_TURN"),
2915
3027
  contextWindowTokens: optional("OPENGENI_CONTEXT_WINDOW_TOKENS"),
@@ -2994,6 +3106,9 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
2994
3106
  lazyToolSearchEnabled: optional("OPENGENI_LAZY_TOOL_SEARCH_ENABLED"),
2995
3107
  codexFleetPolicyShadowEnabled: optional("OPENGENI_CODEX_FLEET_POLICY_SHADOW_ENABLED"),
2996
3108
  codexProductSku: optional("OPENGENI_CODEX_PRODUCT_SKU"),
3109
+ reasoningConfigurationUpdatesEnabled: optional(
3110
+ "OPENGENI_REASONING_CONFIGURATION_UPDATES_ENABLED",
3111
+ ),
2997
3112
  openaiReasoningEffort: optional("OPENGENI_OPENAI_REASONING_EFFORT"),
2998
3113
  openaiAllowedReasoningEfforts: optional("OPENGENI_OPENAI_ALLOWED_REASONING_EFFORTS"),
2999
3114
  openaiResponsesTransport: optional("OPENGENI_OPENAI_RESPONSES_TRANSPORT"),
@@ -3017,6 +3132,7 @@ export function getSettings(source: NodeJS.ProcessEnv = process.env): Settings {
3017
3132
  dockerWorkspaceBaseDir: optional("OPENGENI_DOCKER_WORKSPACE_BASE_DIR"),
3018
3133
  modalAppName: optional("OPENGENI_MODAL_APP_NAME"),
3019
3134
  modalImageRef: optional("OPENGENI_MODAL_IMAGE_REF") ?? DEFAULT_MODAL_IMAGE_REF,
3135
+ modalCommandSupervisionEnabled: optional("OPENGENI_MODAL_COMMAND_SUPERVISION_ENABLED"),
3020
3136
  modalImageId: optional("OPENGENI_MODAL_IMAGE_ID"),
3021
3137
  modalImageRegistrySecret: optional("OPENGENI_MODAL_IMAGE_REGISTRY_SECRET"),
3022
3138
  modalTimeoutSeconds: optional("OPENGENI_MODAL_TIMEOUT_SECONDS"),
@@ -5764,6 +5880,7 @@ export function applyGitAuthPointerEnvironment(
5764
5880
  }
5765
5881
 
5766
5882
  export type StartupRetryOptions = {
5883
+ shouldRetry?: (error: unknown) => boolean;
5767
5884
  attempts?: number;
5768
5885
  initialDelayMs?: number;
5769
5886
  maxDelayMs?: number;
@@ -5778,7 +5895,7 @@ export type StartupRetryOptions = {
5778
5895
 
5779
5896
  export function startupRetryOptions(
5780
5897
  settings: Settings,
5781
- ): Required<Omit<StartupRetryOptions, "onRetry">> {
5898
+ ): Required<Omit<StartupRetryOptions, "onRetry" | "shouldRetry">> {
5782
5899
  return {
5783
5900
  attempts: settings.startupDependencyRetryAttempts,
5784
5901
  initialDelayMs: settings.startupDependencyRetryInitialDelayMs,
@@ -5798,7 +5915,7 @@ export async function retryStartupDependency<T>(
5798
5915
  try {
5799
5916
  return await operation();
5800
5917
  } catch (error) {
5801
- if (attempt >= attempts) {
5918
+ if (attempt >= attempts || options.shouldRetry?.(error) === false) {
5802
5919
  throw error;
5803
5920
  }
5804
5921
  const delayMs = Math.min(maxDelayMs, initialDelayMs * 2 ** (attempt - 1));
@@ -6705,105 +6822,7 @@ function validateSettings(settings: Settings, source: NodeJS.ProcessEnv = proces
6705
6822
  );
6706
6823
  }
6707
6824
  }
6708
- if (
6709
- settings.objectStorageBackend === "s3-compatible" ||
6710
- settings.objectStorageBackend === "aws-s3"
6711
- ) {
6712
- if (
6713
- Boolean(settings.objectStorageAccessKeyId) !== Boolean(settings.objectStorageSecretAccessKey)
6714
- ) {
6715
- throw new Error(
6716
- "OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY must both be set or both omitted",
6717
- );
6718
- }
6719
- if (
6720
- settings.objectStorageBackend === "s3-compatible" &&
6721
- (settings.objectStorageEndpoint ||
6722
- settings.objectStorageInternalEndpoint ||
6723
- settings.objectStorageSandboxEndpoint) &&
6724
- (!settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)
6725
- ) {
6726
- throw new Error(
6727
- "S3-compatible object storage endpoints require OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY",
6728
- );
6729
- }
6730
- if (
6731
- settings.objectStorageAzureConnectionString ||
6732
- settings.objectStorageAzureAccountName ||
6733
- settings.objectStorageAzureAccountKey ||
6734
- settings.objectStorageAzureEndpoint
6735
- ) {
6736
- throw new Error(
6737
- "S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings",
6738
- );
6739
- }
6740
- if (
6741
- settings.objectStorageGcsProjectId ||
6742
- settings.objectStorageGcsCredentialsJson ||
6743
- settings.objectStorageGcsKeyFilename ||
6744
- settings.objectStorageGcsApiEndpoint
6745
- ) {
6746
- throw new Error(
6747
- "S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings",
6748
- );
6749
- }
6750
- } else if (settings.objectStorageBackend === "azure-blob") {
6751
- if (
6752
- settings.objectStorageEndpoint ||
6753
- settings.objectStorageInternalEndpoint ||
6754
- settings.objectStorageSandboxEndpoint ||
6755
- settings.objectStorageAccessKeyId ||
6756
- settings.objectStorageSecretAccessKey
6757
- ) {
6758
- throw new Error(
6759
- "Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not S3-compatible object storage settings",
6760
- );
6761
- }
6762
- if (
6763
- settings.objectStorageGcsProjectId ||
6764
- settings.objectStorageGcsCredentialsJson ||
6765
- settings.objectStorageGcsKeyFilename ||
6766
- settings.objectStorageGcsApiEndpoint
6767
- ) {
6768
- throw new Error(
6769
- "Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings",
6770
- );
6771
- }
6772
- const hasConnectionString = Boolean(settings.objectStorageAzureConnectionString);
6773
- const hasSharedKey =
6774
- Boolean(settings.objectStorageAzureAccountName) &&
6775
- Boolean(settings.objectStorageAzureAccountKey);
6776
- if (!hasConnectionString && !hasSharedKey) {
6777
- throw new Error(
6778
- "Azure Blob storage requires OPENGENI_OBJECT_STORAGE_AZURE_CONNECTION_STRING or OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_NAME plus OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_KEY",
6779
- );
6780
- }
6781
- } else {
6782
- if (
6783
- settings.objectStorageEndpoint ||
6784
- settings.objectStorageInternalEndpoint ||
6785
- settings.objectStorageSandboxEndpoint ||
6786
- settings.objectStorageAccessKeyId ||
6787
- settings.objectStorageSecretAccessKey
6788
- ) {
6789
- throw new Error(
6790
- "GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not S3-compatible object storage settings",
6791
- );
6792
- }
6793
- if (
6794
- settings.objectStorageAzureConnectionString ||
6795
- settings.objectStorageAzureAccountName ||
6796
- settings.objectStorageAzureAccountKey ||
6797
- settings.objectStorageAzureEndpoint
6798
- ) {
6799
- throw new Error(
6800
- "GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings",
6801
- );
6802
- }
6803
- if (settings.objectStorageGcsCredentialsJson) {
6804
- parseGcsCredentialsJson(settings.objectStorageGcsCredentialsJson);
6805
- }
6806
- }
6825
+ validateObjectStorageSettings(settings);
6807
6826
  if (settings.documentChunkOverlap >= settings.documentChunkSize) {
6808
6827
  throw new Error(
6809
6828
  "OPENGENI_DOCUMENT_CHUNK_OVERLAP must be smaller than OPENGENI_DOCUMENT_CHUNK_SIZE",
@@ -6832,12 +6851,9 @@ function validateSettings(settings: Settings, source: NodeJS.ProcessEnv = proces
6832
6851
  throw new Error(`OPENGENI_MCP_SERVERS contains duplicate id ${server.id}`);
6833
6852
  }
6834
6853
  serverIds.add(server.id);
6835
- if (
6836
- server.connectionRef?.authoritySource === "host" &&
6837
- !settings.hostMcpAuthoritySourceAdmissionEnabled
6838
- ) {
6854
+ if (server.connectionRef?.authoritySource === "host") {
6839
6855
  throw new Error(
6840
- "OPENGENI_MCP_SERVERS host-owned connection refs require OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED=true after the whole API/worker fleet is upgraded",
6856
+ "OPENGENI_MCP_SERVERS host-owned connection refs are no longer supported; select ordinary native connections",
6841
6857
  );
6842
6858
  }
6843
6859
  }
@@ -7191,6 +7207,108 @@ export function resolveNatsCalloutConfig(settings: Settings): NatsCalloutConfig
7191
7207
  return { accountSeed, accountName, user, password };
7192
7208
  }
7193
7209
 
7210
+ function validateObjectStorageSettings(settings: ObjectStorageSettings): void {
7211
+ if (
7212
+ settings.objectStorageBackend === "s3-compatible" ||
7213
+ settings.objectStorageBackend === "aws-s3"
7214
+ ) {
7215
+ if (
7216
+ Boolean(settings.objectStorageAccessKeyId) !== Boolean(settings.objectStorageSecretAccessKey)
7217
+ ) {
7218
+ throw new Error(
7219
+ "OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY must both be set or both omitted",
7220
+ );
7221
+ }
7222
+ if (
7223
+ settings.objectStorageBackend === "s3-compatible" &&
7224
+ (settings.objectStorageEndpoint ||
7225
+ settings.objectStorageInternalEndpoint ||
7226
+ settings.objectStorageSandboxEndpoint) &&
7227
+ (!settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)
7228
+ ) {
7229
+ throw new Error(
7230
+ "S3-compatible object storage endpoints require OPENGENI_OBJECT_STORAGE_ACCESS_KEY_ID and OPENGENI_OBJECT_STORAGE_SECRET_ACCESS_KEY",
7231
+ );
7232
+ }
7233
+ if (
7234
+ settings.objectStorageAzureConnectionString ||
7235
+ settings.objectStorageAzureAccountName ||
7236
+ settings.objectStorageAzureAccountKey ||
7237
+ settings.objectStorageAzureEndpoint
7238
+ ) {
7239
+ throw new Error(
7240
+ "S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings",
7241
+ );
7242
+ }
7243
+ if (
7244
+ settings.objectStorageGcsProjectId ||
7245
+ settings.objectStorageGcsCredentialsJson ||
7246
+ settings.objectStorageGcsKeyFilename ||
7247
+ settings.objectStorageGcsApiEndpoint
7248
+ ) {
7249
+ throw new Error(
7250
+ "S3 object storage uses OPENGENI_OBJECT_STORAGE_* S3 settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings",
7251
+ );
7252
+ }
7253
+ } else if (settings.objectStorageBackend === "azure-blob") {
7254
+ if (
7255
+ settings.objectStorageEndpoint ||
7256
+ settings.objectStorageInternalEndpoint ||
7257
+ settings.objectStorageSandboxEndpoint ||
7258
+ settings.objectStorageAccessKeyId ||
7259
+ settings.objectStorageSecretAccessKey
7260
+ ) {
7261
+ throw new Error(
7262
+ "Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not S3-compatible object storage settings",
7263
+ );
7264
+ }
7265
+ if (
7266
+ settings.objectStorageGcsProjectId ||
7267
+ settings.objectStorageGcsCredentialsJson ||
7268
+ settings.objectStorageGcsKeyFilename ||
7269
+ settings.objectStorageGcsApiEndpoint
7270
+ ) {
7271
+ throw new Error(
7272
+ "Azure Blob storage uses OPENGENI_OBJECT_STORAGE_AZURE_* settings, not OPENGENI_OBJECT_STORAGE_GCS_* settings",
7273
+ );
7274
+ }
7275
+ const hasConnectionString = Boolean(settings.objectStorageAzureConnectionString);
7276
+ const hasSharedKey =
7277
+ Boolean(settings.objectStorageAzureAccountName) &&
7278
+ Boolean(settings.objectStorageAzureAccountKey);
7279
+ if (!hasConnectionString && !hasSharedKey) {
7280
+ throw new Error(
7281
+ "Azure Blob storage requires OPENGENI_OBJECT_STORAGE_AZURE_CONNECTION_STRING or OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_NAME plus OPENGENI_OBJECT_STORAGE_AZURE_ACCOUNT_KEY",
7282
+ );
7283
+ }
7284
+ } else {
7285
+ if (
7286
+ settings.objectStorageEndpoint ||
7287
+ settings.objectStorageInternalEndpoint ||
7288
+ settings.objectStorageSandboxEndpoint ||
7289
+ settings.objectStorageAccessKeyId ||
7290
+ settings.objectStorageSecretAccessKey
7291
+ ) {
7292
+ throw new Error(
7293
+ "GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not S3-compatible object storage settings",
7294
+ );
7295
+ }
7296
+ if (
7297
+ settings.objectStorageAzureConnectionString ||
7298
+ settings.objectStorageAzureAccountName ||
7299
+ settings.objectStorageAzureAccountKey ||
7300
+ settings.objectStorageAzureEndpoint
7301
+ ) {
7302
+ throw new Error(
7303
+ "GCS object storage uses OPENGENI_OBJECT_STORAGE_GCS_* settings, not OPENGENI_OBJECT_STORAGE_AZURE_* settings",
7304
+ );
7305
+ }
7306
+ if (settings.objectStorageGcsCredentialsJson) {
7307
+ parseGcsCredentialsJson(settings.objectStorageGcsCredentialsJson);
7308
+ }
7309
+ }
7310
+ }
7311
+
7194
7312
  /**
7195
7313
  * The PRIVILEGED control-plane NATS login (api/worker). Present only when BOTH a
7196
7314
  * user and password are set; otherwise null and the bus connects anonymously (local
@@ -7202,7 +7320,9 @@ export interface NatsControlPlaneAuth {
7202
7320
  password: string;
7203
7321
  }
7204
7322
 
7205
- export function resolveNatsControlPlaneAuth(settings: Settings): NatsControlPlaneAuth | null {
7323
+ export function resolveNatsControlPlaneAuth(
7324
+ settings: Pick<Settings, "selfhostedNatsControlUser" | "selfhostedNatsControlPassword">,
7325
+ ): NatsControlPlaneAuth | null {
7206
7326
  const user = settings.selfhostedNatsControlUser?.trim();
7207
7327
  const password = settings.selfhostedNatsControlPassword?.trim();
7208
7328
  if (!user || !password) {