@hivemagnus/magnus-server 0.0.0-stage → 1.0.2
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/.env.example +171 -0
- package/LICENSE +202 -0
- package/README.md +169 -2
- package/agent-version.json +1 -0
- package/dist/ai/HEARTBEAT.md +56 -0
- package/dist/ai/IDENTITY.md +54 -0
- package/dist/ai/MEMORY.md +40 -0
- package/dist/ai/TOOLS.md +538 -0
- package/dist/ai/USER.md +32 -0
- package/dist/cli/args.js +62 -0
- package/dist/cli/env-catalog.js +131 -0
- package/dist/cli/preflight.js +89 -0
- package/dist/cli.js +175 -0
- package/dist/config/cors.js +16 -0
- package/dist/config/env.js +392 -0
- package/dist/config/features.js +16 -0
- package/dist/config/index.js +3 -0
- package/dist/config/listener-security.js +38 -0
- package/dist/config/version.js +31 -0
- package/dist/db/client.js +44 -0
- package/dist/db/generated/prisma/browser.js +18 -0
- package/dist/db/generated/prisma/client.js +35 -0
- package/dist/db/generated/prisma/commonInputTypes.js +11 -0
- package/dist/db/generated/prisma/enums.js +42 -0
- package/dist/db/generated/prisma/internal/class.js +50 -0
- package/dist/db/generated/prisma/internal/prismaNamespace.js +1598 -0
- package/dist/db/generated/prisma/internal/prismaNamespaceBrowser.js +1569 -0
- package/dist/db/generated/prisma/models/A2ATask.js +2 -0
- package/dist/db/generated/prisma/models/AdmissionFence.js +2 -0
- package/dist/db/generated/prisma/models/Agent.js +2 -0
- package/dist/db/generated/prisma/models/AiConversation.js +2 -0
- package/dist/db/generated/prisma/models/AiDashboard.js +2 -0
- package/dist/db/generated/prisma/models/AiMemory.js +2 -0
- package/dist/db/generated/prisma/models/AiMessage.js +2 -0
- package/dist/db/generated/prisma/models/AiScheduledOp.js +2 -0
- package/dist/db/generated/prisma/models/AlertConditionState.js +2 -0
- package/dist/db/generated/prisma/models/AlertRule.js +2 -0
- package/dist/db/generated/prisma/models/AlertViolation.js +2 -0
- package/dist/db/generated/prisma/models/AnomalyEvent.js +2 -0
- package/dist/db/generated/prisma/models/ApplicationObservation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentInstallation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentProfile.js +2 -0
- package/dist/db/generated/prisma/models/ArdentScopeEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/CommandResult.js +2 -0
- package/dist/db/generated/prisma/models/CronJob.js +2 -0
- package/dist/db/generated/prisma/models/CronJobExecution.js +2 -0
- package/dist/db/generated/prisma/models/DeployJob.js +2 -0
- package/dist/db/generated/prisma/models/DriftScan.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringMetricSample.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringRecoveryExercise.js +2 -0
- package/dist/db/generated/prisma/models/ExecutionRun.js +2 -0
- package/dist/db/generated/prisma/models/FilePatchTask.js +2 -0
- package/dist/db/generated/prisma/models/GpuSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HealthSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRollout.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRolloutHost.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPool.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPoolAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesCronJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesHook.js +2 -0
- package/dist/db/generated/prisma/models/HermesInstallation.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServer.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServerAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesMessagingBinding.js +2 -0
- package/dist/db/generated/prisma/models/HermesOperationJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesPluginInstall.js +2 -0
- package/dist/db/generated/prisma/models/HermesProfile.js +2 -0
- package/dist/db/generated/prisma/models/HermesProviderConfig.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryClient.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryInventory.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryReceipt.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryRevision.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryTarget.js +2 -0
- package/dist/db/generated/prisma/models/HermesRole.js +2 -0
- package/dist/db/generated/prisma/models/HermesRoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/HermesSkillAssignment.js +2 -0
- package/dist/db/generated/prisma/models/HermesToolsetPolicy.js +2 -0
- package/dist/db/generated/prisma/models/IncidentLesson.js +2 -0
- package/dist/db/generated/prisma/models/LibraryCategory.js +2 -0
- package/dist/db/generated/prisma/models/LibraryFile.js +2 -0
- package/dist/db/generated/prisma/models/LogEntry.js +2 -0
- package/dist/db/generated/prisma/models/MagnusReleaseCanary.js +2 -0
- package/dist/db/generated/prisma/models/MaintenanceWindow.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronDefinition.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronOccurrence.js +2 -0
- package/dist/db/generated/prisma/models/Node.js +2 -0
- package/dist/db/generated/prisma/models/Notification.js +2 -0
- package/dist/db/generated/prisma/models/NotificationPreference.js +2 -0
- package/dist/db/generated/prisma/models/RemediationExecution.js +2 -0
- package/dist/db/generated/prisma/models/RemediationRule.js +2 -0
- package/dist/db/generated/prisma/models/RemoteCommand.js +2 -0
- package/dist/db/generated/prisma/models/ResourceDrainParticipant.js +2 -0
- package/dist/db/generated/prisma/models/ResourceEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/ResourceParticipantWork.js +2 -0
- package/dist/db/generated/prisma/models/Role.js +2 -0
- package/dist/db/generated/prisma/models/RoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/RolloutTarget.js +2 -0
- package/dist/db/generated/prisma/models/SecurityExportOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionAction.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionActionApproval.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCase.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseActivity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseRelationship.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCollector.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCommandOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDeletionReceipt.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDelivery.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntityAlias.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEventEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvidence.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionInvestigation.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionNonce.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionReceiptChainHead.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionRuleVersion.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionSourceState.js +2 -0
- package/dist/db/generated/prisma/models/SecurityServiceState.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLeaseResource.js +2 -0
- package/dist/db/generated/prisma/models/SkillInstallation.js +2 -0
- package/dist/db/generated/prisma/models/SkillRegistryEntry.js +2 -0
- package/dist/db/generated/prisma/models/SkillTag.js +2 -0
- package/dist/db/generated/prisma/models/SkillVersion.js +2 -0
- package/dist/db/generated/prisma/models/TeamApprovalDecision.js +2 -0
- package/dist/db/generated/prisma/models/User.js +2 -0
- package/dist/db/generated/prisma/models/WebhookDelivery.js +2 -0
- package/dist/db/generated/prisma/models/WebhookEndpoint.js +2 -0
- package/dist/db/generated/prisma/models.js +2 -0
- package/dist/db/index.js +3 -0
- package/dist/db/read-retry.js +35 -0
- package/dist/db/seed.js +348 -0
- package/dist/index.js +162 -0
- package/dist/middleware/auth.js +361 -0
- package/dist/middleware/body-limit.js +14 -0
- package/dist/middleware/correlation-id.js +13 -0
- package/dist/middleware/error-handler.js +28 -0
- package/dist/middleware/iam-token.js +21 -0
- package/dist/middleware/rate-limit.js +78 -0
- package/dist/middleware/request-logger.js +85 -0
- package/dist/middleware/security-headers.js +23 -0
- package/dist/middleware/tracing.js +120 -0
- package/dist/middleware/user-status.js +33 -0
- package/dist/openapi/spec.js +2656 -0
- package/dist/queue/redis.js +20 -0
- package/dist/queue/requeue.js +27 -0
- package/dist/queue/workers.js +342 -0
- package/dist/routes/ai-schedule.js +73 -0
- package/dist/routes/alerts.js +79 -0
- package/dist/routes/anomalies.js +101 -0
- package/dist/routes/ardent.js +82 -0
- package/dist/routes/assistant.js +417 -0
- package/dist/routes/audit.js +138 -0
- package/dist/routes/codex.js +65 -0
- package/dist/routes/commands.js +226 -0
- package/dist/routes/credentials.js +58 -0
- package/dist/routes/cron.js +292 -0
- package/dist/routes/dependencies.js +49 -0
- package/dist/routes/deploy.js +18 -0
- package/dist/routes/discovery.js +168 -0
- package/dist/routes/docs.js +24 -0
- package/dist/routes/drift.js +126 -0
- package/dist/routes/events-stream.js +87 -0
- package/dist/routes/files.js +214 -0
- package/dist/routes/fleet-checks.js +21 -0
- package/dist/routes/health.js +133 -0
- package/dist/routes/heartbeat.js +545 -0
- package/dist/routes/hermes-registry.js +278 -0
- package/dist/routes/hermes.js +61 -0
- package/dist/routes/library.js +402 -0
- package/dist/routes/logs.js +172 -0
- package/dist/routes/managed-cron.js +23 -0
- package/dist/routes/mcp.js +39 -0
- package/dist/routes/metrics.js +187 -0
- package/dist/routes/nodes.js +951 -0
- package/dist/routes/notifications.js +130 -0
- package/dist/routes/ops.js +258 -0
- package/dist/routes/orchestra.js +430 -0
- package/dist/routes/predictions.js +23 -0
- package/dist/routes/profiling.js +68 -0
- package/dist/routes/quick-command.js +72 -0
- package/dist/routes/remediation.js +111 -0
- package/dist/routes/resource-leases.js +297 -0
- package/dist/routes/roles.js +370 -0
- package/dist/routes/rollout.js +124 -0
- package/dist/routes/scan.js +116 -0
- package/dist/routes/security-backup.js +58 -0
- package/dist/routes/security-fusion.js +605 -0
- package/dist/routes/security-integration.js +71 -0
- package/dist/routes/self-update.js +129 -0
- package/dist/routes/shared-resource-leases.js +78 -0
- package/dist/routes/skills.js +485 -0
- package/dist/routes/stats.js +96 -0
- package/dist/routes/team-approvals.js +23 -0
- package/dist/routes/terminal-ticket.js +15 -0
- package/dist/routes/terminal.js +17 -0
- package/dist/routes/topology.js +37 -0
- package/dist/routes/users.js +219 -0
- package/dist/routes/webhooks.js +137 -0
- package/dist/schemas/command-reconciliation.js +14 -0
- package/dist/schemas/commands.js +51 -0
- package/dist/schemas/cron.js +33 -0
- package/dist/schemas/deploy.js +20 -0
- package/dist/schemas/files.js +14 -0
- package/dist/schemas/hermes.js +69 -0
- package/dist/schemas/index.js +11 -0
- package/dist/schemas/library.js +48 -0
- package/dist/schemas/logs.js +35 -0
- package/dist/schemas/managed-cron.js +127 -0
- package/dist/schemas/node-responses.js +87 -0
- package/dist/schemas/node-session-receipt.js +84 -0
- package/dist/schemas/nodes.js +36 -0
- package/dist/schemas/orchestra.js +118 -0
- package/dist/schemas/resource-participant-reconciliation.js +99 -0
- package/dist/schemas/roles.js +54 -0
- package/dist/schemas/shared-resource-lease.js +132 -0
- package/dist/schemas/skills.js +44 -0
- package/dist/schemas/users.js +28 -0
- package/dist/scripts/run-disposable-recovery-exercise.js +39 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/node-api-client.js +474 -0
- package/dist/security/approval-assertion.js +66 -0
- package/dist/security/command-completion-registry.js +35 -0
- package/dist/security/command-request.js +12 -0
- package/dist/security/command-result-signature.js +34 -0
- package/dist/security/managed-http.js +45 -0
- package/dist/security/native-completion-registry.js +44 -0
- package/dist/server.js +178 -0
- package/dist/services/admission-fence.js +71 -0
- package/dist/services/ai-assistant-service.js +2523 -0
- package/dist/services/ai-correlation-service.js +225 -0
- package/dist/services/ai-export-service.js +141 -0
- package/dist/services/ai-insights-service.js +219 -0
- package/dist/services/ai-schedule-service.js +145 -0
- package/dist/services/ai-scratchpad-service.js +222 -0
- package/dist/services/ai-tools.js +1343 -0
- package/dist/services/alert-service.js +313 -0
- package/dist/services/anomaly-detector.js +419 -0
- package/dist/services/api-local-work.js +79 -0
- package/dist/services/application-probes.js +309 -0
- package/dist/services/ardent-scope-enrollment.js +123 -0
- package/dist/services/ardent-service.js +86 -0
- package/dist/services/audit-service.js +308 -0
- package/dist/services/codex-service.js +209 -0
- package/dist/services/command-lifecycle.js +60 -0
- package/dist/services/command-resource-leases.js +305 -0
- package/dist/services/control-host-node.js +242 -0
- package/dist/services/credential-rotation-service.js +294 -0
- package/dist/services/dependency-service.js +145 -0
- package/dist/services/disposable-recovery-runner.js +595 -0
- package/dist/services/docker-group-health.js +121 -0
- package/dist/services/engineering-assurance.js +492 -0
- package/dist/services/engineering-evidence-ledger.js +94 -0
- package/dist/services/engineering-metric-evidence.js +26 -0
- package/dist/services/event-bus.js +227 -0
- package/dist/services/fleet-check-log.js +102 -0
- package/dist/services/forecast-service.js +154 -0
- package/dist/services/health-retention.js +175 -0
- package/dist/services/heartbeat-stream.js +63 -0
- package/dist/services/hermes-catalog-import.js +62 -0
- package/dist/services/hermes-cli-parser.js +671 -0
- package/dist/services/hermes-core-rollout.js +90 -0
- package/dist/services/hermes-dashboard-endpoint.js +13 -0
- package/dist/services/hermes-plugin-source.js +149 -0
- package/dist/services/hermes-registry-service.js +258 -0
- package/dist/services/hermes-service.js +566 -0
- package/dist/services/hivemem-client.js +85 -0
- package/dist/services/host-local-writer.js +82 -0
- package/dist/services/incident-evidence.js +110 -0
- package/dist/services/incident-lessons.js +186 -0
- package/dist/services/llm-providers/anthropic.js +143 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llama-cpp.js +237 -0
- package/dist/services/llm-providers/ollama.js +292 -0
- package/dist/services/llm-providers/openai-compatible.js +200 -0
- package/dist/services/llm-providers/provider-error.js +36 -0
- package/dist/services/llm-providers/registry.js +45 -0
- package/dist/services/llm-providers/types.js +2 -0
- package/dist/services/log-emitter.js +19 -0
- package/dist/services/log-retention.js +66 -0
- package/dist/services/maintenance-service.js +29 -0
- package/dist/services/managed-cron.js +493 -0
- package/dist/services/managed-ssh-admission.js +68 -0
- package/dist/services/monitor-pulse.js +10 -0
- package/dist/services/node-issue-monitor.js +71 -0
- package/dist/services/node-service.js +35 -0
- package/dist/services/notification-retention.js +51 -0
- package/dist/services/notification-service.js +94 -0
- package/dist/services/operational-observations.js +112 -0
- package/dist/services/operations-config.js +67 -0
- package/dist/services/ops-service.js +491 -0
- package/dist/services/orchestra-extras-service.js +370 -0
- package/dist/services/orchestra-resources-service.js +530 -0
- package/dist/services/os-scheduler-writer.js +73 -0
- package/dist/services/password-service.js +22 -0
- package/dist/services/prediction-service.js +284 -0
- package/dist/services/recovery-exercises.js +682 -0
- package/dist/services/remediation-service.js +325 -0
- package/dist/services/resource-enrollment.js +433 -0
- package/dist/services/resource-node-lock.js +18 -0
- package/dist/services/resource-participant-work.js +302 -0
- package/dist/services/role-service.js +388 -0
- package/dist/services/rollout-service.js +440 -0
- package/dist/services/security-export.js +185 -0
- package/dist/services/security-fusion-actions.js +22 -0
- package/dist/services/security-fusion-approvals.js +83 -0
- package/dist/services/security-fusion-evidence-integrity.js +144 -0
- package/dist/services/security-fusion-ingest.js +101 -0
- package/dist/services/security-fusion-outbox.js +185 -0
- package/dist/services/security-fusion-receipt-chain.js +134 -0
- package/dist/services/security-fusion-service.js +751 -0
- package/dist/services/security-fusion-verification.js +50 -0
- package/dist/services/self-update-service.js +200 -0
- package/dist/services/shared-resource-leases.js +413 -0
- package/dist/services/skill-publication-bundle.js +90 -0
- package/dist/services/skill-publication-settlement.js +36 -0
- package/dist/services/skill-publication-state.js +40 -0
- package/dist/services/skill-registry-service.js +327 -0
- package/dist/services/skill-service.js +148 -0
- package/dist/services/ssh-exec-service.js +139 -0
- package/dist/services/ssh-host-verification.js +27 -0
- package/dist/services/tailscale-node-key-service.js +75 -0
- package/dist/services/team-approvals.js +118 -0
- package/dist/services/topology-service.js +138 -0
- package/dist/services/transfer-authority-snapshot.js +151 -0
- package/dist/services/webhook-credential-migration.js +97 -0
- package/dist/services/webhook-security.js +242 -0
- package/dist/services/webhook-service.js +494 -0
- package/dist/types/auth.js +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/library.js +3 -0
- package/dist/types/notifications.js +3 -0
- package/dist/types/roles.js +3 -0
- package/dist/types/shared-contracts.js +3 -0
- package/dist/utils/env.js +141 -0
- package/dist/utils/heartbeat-peer.js +19 -0
- package/dist/utils/idempotency.js +45 -0
- package/dist/utils/ip.js +25 -0
- package/dist/utils/json-fields.js +94 -0
- package/dist/utils/local-tls.js +39 -0
- package/dist/utils/magnus-home.js +15 -0
- package/dist/utils/node-url.js +59 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/safe-equal.js +13 -0
- package/dist/utils/sentinel-nodes.js +16 -0
- package/dist/utils/validate.js +41 -0
- package/package.json +119 -4
- package/prisma/migrations/20261031000000_baseline/migration.sql +2935 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +2185 -0
- package/prisma.config.ts +14 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
// ─── Shared environment schema — validated with Zod ───
|
|
4
|
+
const portSchema = z.coerce.number().int().min(1).max(65535);
|
|
5
|
+
const booleanEnvSchema = z.enum(['true', 'false']).default('false').transform((value) => value === 'true');
|
|
6
|
+
function configuredDigestValues(raw) {
|
|
7
|
+
if (!raw)
|
|
8
|
+
return [];
|
|
9
|
+
try {
|
|
10
|
+
const value = JSON.parse(raw);
|
|
11
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
12
|
+
? Object.values(value).filter((entry) => typeof entry === 'string' && /^[a-f0-9]{64}$/.test(entry))
|
|
13
|
+
: [];
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Default skills directory — `<package root>/skills`. */
|
|
20
|
+
const DEFAULT_SKILLS_DIR = path.resolve(import.meta.dirname, '../../skills');
|
|
21
|
+
export const envSchema = z.object({
|
|
22
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
23
|
+
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error', 'fatal']).default('info'),
|
|
24
|
+
// PostgreSQL
|
|
25
|
+
DATABASE_URL: z.string().regex(/^postgres(?:ql)?:\/\//).optional(),
|
|
26
|
+
DATABASE_POOL_SIZE: z.coerce.number().int().min(1).max(100).default(20),
|
|
27
|
+
// Redis (local)
|
|
28
|
+
REDIS_HOST: z.string().default('127.0.0.1'),
|
|
29
|
+
REDIS_PORT: portSchema.default(6379),
|
|
30
|
+
REDIS_URL: z.string().startsWith('redis://').optional(),
|
|
31
|
+
REDIS_PREFIX: z.string().default('magnus:'),
|
|
32
|
+
// Retired master secret. Production rejects this key so credentials cannot
|
|
33
|
+
// silently collapse back into a cross-protocol trust domain.
|
|
34
|
+
MAGNUS_SECRET: z.string().optional(),
|
|
35
|
+
// Dedicated audit-chain key. It must never fall back to an API/JWT secret.
|
|
36
|
+
AUDIT_HMAC_SECRET: z.string().min(32).optional(),
|
|
37
|
+
// Control API
|
|
38
|
+
CONTROL_API_PORT: portSchema.default(4000),
|
|
39
|
+
CONTROL_API_URL: z.string().url().default('https://localhost:4000'),
|
|
40
|
+
API_SECRET: z.string().min(32).optional(),
|
|
41
|
+
MAGNUS_COLLECTOR_TOKEN_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
42
|
+
MAGNUS_RESOURCE_LEASES_ENABLED: booleanEnvSchema,
|
|
43
|
+
RESOURCE_ENROLLMENT_DASHBOARD_SECRET: z.string().min(32).optional(),
|
|
44
|
+
// Optional signed team-approval relay (Ed25519 public key + actor bindings).
|
|
45
|
+
APPROVAL_SIGNER_PUBLIC_KEY_PEM: z.string().optional(),
|
|
46
|
+
APPROVAL_SIGNER_PUBLIC_KEY_B64: z.string().min(32).optional(),
|
|
47
|
+
APPROVAL_SIGNER_PUBLIC_KEY_PATH: z.string().optional(),
|
|
48
|
+
APPROVAL_TELEGRAM_ACTOR_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
49
|
+
APPROVAL_IPHONE_ACTOR_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
50
|
+
MAGNUS_APPROVAL_BRIDGE_CONSUMER: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/i).optional(),
|
|
51
|
+
// Security Fusion: off (default) | local (in-process) | remote (external security console).
|
|
52
|
+
SECURITY_FUSION_MODE: z.enum(['off', 'local', 'remote']).default('off'),
|
|
53
|
+
SECURITY_FUSION_API_URL: z.string().url().optional(),
|
|
54
|
+
MAGNUS_SECURITY_CONSOLE_URL: z.string().url().optional(),
|
|
55
|
+
SECURITY_APPROVAL_BRIDGE_TOKEN: z.string().min(32).optional(),
|
|
56
|
+
COMMAND_RECONCILIATION_SECRET: z.string().min(32).optional(),
|
|
57
|
+
RESOURCE_PARTICIPANT_RECONCILIATION_SECRET: z.string().regex(/^[A-Za-z0-9._~+/=-]{32,4096}$/).optional(),
|
|
58
|
+
SHARED_RESOURCE_RECONCILIATION_SECRET: z.string().regex(/^[A-Za-z0-9._~+/=-]{32,4096}$/).optional(),
|
|
59
|
+
SHARED_RESOURCE_MISSION_TRUST_JSON: z.string().optional(),
|
|
60
|
+
SHARED_RESOURCE_RESULT_TRUST_JSON: z.string().optional(),
|
|
61
|
+
DASHBOARD_PUBLIC_ORIGIN: z.string().url().optional(),
|
|
62
|
+
MAGNUS_APPLICATION_PROBES_FILE: z.string().optional(),
|
|
63
|
+
MAGNUS_OPERATIONS_CATALOG_FILE: z.string().optional(),
|
|
64
|
+
MAGNUS_ENGINEERING_EVIDENCE_FILE: z.string().optional(),
|
|
65
|
+
MAGNUS_ENGINEERING_EVIDENCE_HEAD_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
66
|
+
MAGNUS_RECOVERY_RUNNER_PUBLIC_KEY_FILE: z.string().optional(),
|
|
67
|
+
MAGNUS_LESSONS_HIVEMEM_URL: z.string().url().optional(),
|
|
68
|
+
MAGNUS_LESSONS_HIVEMEM_TOKEN: z.string().optional(),
|
|
69
|
+
MAGNUS_LESSONS_WORKSPACE: z.string().optional(),
|
|
70
|
+
// CORS — comma-separated origins allowed to call the Control API
|
|
71
|
+
CORS_ORIGIN: z.string().default('https://localhost:3000'),
|
|
72
|
+
// Listener security. Cleartext is development-only and must be explicitly
|
|
73
|
+
// enabled; the runtime additionally confines it to loopback.
|
|
74
|
+
CONTROL_API_BIND_HOST: z.string().min(1).optional(),
|
|
75
|
+
ALLOW_INSECURE_HTTP: booleanEnvSchema,
|
|
76
|
+
// Node Manager
|
|
77
|
+
NODE_MANAGER_PORT: portSchema.default(4100),
|
|
78
|
+
NODE_MANAGER_SECRET: z.string().min(32).optional(),
|
|
79
|
+
AGENT_TLS_DOMAIN_SUFFIX: z
|
|
80
|
+
.string()
|
|
81
|
+
.regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i)
|
|
82
|
+
.optional(),
|
|
83
|
+
ENABLE_AGENT_DISCOVERY: booleanEnvSchema,
|
|
84
|
+
AGENT_DISCOVERY_SECRET: z.string().min(32).optional(),
|
|
85
|
+
CREDENTIAL_ROTATION_KEY: z.string().min(32).optional(),
|
|
86
|
+
CREDENTIAL_ROTATION_STATE_PATH: z.string().optional(),
|
|
87
|
+
// Auth / JWT
|
|
88
|
+
JWT_SECRET: z.string().min(32).optional(),
|
|
89
|
+
RESULT_SIGNING_SECRET: z.string().min(32).optional(),
|
|
90
|
+
RESULT_SIGNING_PREVIOUS_SECRET: z.string().min(32).optional(),
|
|
91
|
+
LOG_INGEST_SIGNING_SECRET: z.string().min(32).optional(),
|
|
92
|
+
LOG_INGEST_PREVIOUS_SIGNING_SECRET: z.string().min(32).optional(),
|
|
93
|
+
MAGNUS_PLUGIN_API_ENABLED: booleanEnvSchema,
|
|
94
|
+
MAGNUS_PLUGIN_TOKEN_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
95
|
+
MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON: z.string().optional(),
|
|
96
|
+
MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
97
|
+
MAGNUS_SHARED_RESOURCE_TOKEN_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
98
|
+
MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON: z.string().optional(),
|
|
99
|
+
MAGNUS_FLEET_CHECK_TOKEN_SHA256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
100
|
+
SECURITY_FUSION_RECEIPT_SIGNING_KEY: z.string().min(32).optional(),
|
|
101
|
+
METRICS_TOKEN: z.string().min(32).optional(),
|
|
102
|
+
WEBHOOK_ENCRYPTION_KEY: z.string().min(32).optional(),
|
|
103
|
+
WEBHOOK_ALLOWED_ORIGINS: z.string().default(''),
|
|
104
|
+
JWT_EXPIRY: z.string().default('24h'),
|
|
105
|
+
IAM_CORE_URL: z.string().url().optional(),
|
|
106
|
+
IAM_TOKEN_ISSUER: z.string().min(1).optional(),
|
|
107
|
+
IAM_TOKEN_AUDIENCE: z.string().min(1).optional(),
|
|
108
|
+
// Skills
|
|
109
|
+
SKILLS_DIR: z.string().default(DEFAULT_SKILLS_DIR),
|
|
110
|
+
// Log retention
|
|
111
|
+
LOG_RETENTION_DAYS: z.coerce.number().int().min(1).default(30),
|
|
112
|
+
HEALTH_SNAPSHOT_RETENTION_DAYS: z.coerce.number().int().min(2).default(7),
|
|
113
|
+
HEALTH_SNAPSHOT_FULL_FIDELITY_HOURS: z.coerce.number().int().min(1).default(24),
|
|
114
|
+
HEALTH_SNAPSHOT_SAMPLE_MINUTES: z.coerce.number().int().min(1).default(15),
|
|
115
|
+
ENGINEERING_EXERCISE_RETENTION_DAYS: z.coerce.number().int().min(30).default(90),
|
|
116
|
+
NOTIFICATION_READ_RETENTION_DAYS: z.coerce.number().int().min(1).default(30),
|
|
117
|
+
NOTIFICATION_MAX_RETENTION_DAYS: z.coerce.number().int().min(1).default(90),
|
|
118
|
+
ANOMALY_BASELINE_CACHE_MS: z.coerce.number().int().min(30_000).default(300_000),
|
|
119
|
+
MAGNUS_FLEET_CHECK_LOG_DIR: z.string().optional(),
|
|
120
|
+
// TLS
|
|
121
|
+
TLS_CERT_PATH: z.string().optional(),
|
|
122
|
+
TLS_KEY_PATH: z.string().optional(),
|
|
123
|
+
CONTROL_API_CA_PATH: z.string().optional(),
|
|
124
|
+
CONTROL_API_TLS_SERVERNAME: z.string().optional(),
|
|
125
|
+
CONTROL_HOST_AGENT_CA_PATH: z.string().optional(),
|
|
126
|
+
CONTROL_HOST_AGENT_TLS_SERVERNAME: z.string().optional(),
|
|
127
|
+
// Deploy credentials (write-only — never log or expose in API responses)
|
|
128
|
+
SSH_USERNAME: z.string().optional(),
|
|
129
|
+
SSH_PASSWORD: z.string().optional(),
|
|
130
|
+
SSH_PRIVATE_KEY_PATH: z.string().optional(),
|
|
131
|
+
SSH_HOST_KEY_PINS_FILE: z.string().optional(),
|
|
132
|
+
MAGNUS_DOCKER_USER: z.string().optional(),
|
|
133
|
+
// HiveMem (shared agent memory)
|
|
134
|
+
HIVEMEM_URL: z.string().url().optional(),
|
|
135
|
+
HIVEMEM_TOKEN: z.string().optional(),
|
|
136
|
+
// Managed Hermes Agent fleet configuration (all optional)
|
|
137
|
+
MAGNUS_HERMES_REGISTRY_ENABLED: booleanEnvSchema,
|
|
138
|
+
MAGNUS_REGISTRY_DEPLOY_HOSTS: z.string().default(''),
|
|
139
|
+
MAGNUS_NODE_DNS_SUFFIX: z
|
|
140
|
+
.string()
|
|
141
|
+
.regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i)
|
|
142
|
+
.optional(),
|
|
143
|
+
HIVE_ROUTER_URL: z.string().url().optional(),
|
|
144
|
+
HIVE_ROUTER_HEALTH_URL: z.string().url().optional(),
|
|
145
|
+
HIVE_ROUTER_MODEL: z.string().optional(),
|
|
146
|
+
HIVE_ROUTER_API_KEY: z.string().optional(),
|
|
147
|
+
MAGNUS_ROUTER_URL: z.string().url().optional(),
|
|
148
|
+
MAGNUS_ROUTER_MODEL: z.string().optional(),
|
|
149
|
+
MAGNUS_ROUTER_API_KEY: z.string().optional(),
|
|
150
|
+
MAGNUS_SKILLS_HIVEMEM_PLUGIN_DIR: z.string().optional(),
|
|
151
|
+
HERMES_HIVEMEM_WORKSPACE_ID: z.string().optional(),
|
|
152
|
+
HERMES_HIVEMEM_DEFAULT_SCOPE: z.enum(['private', 'shared']).optional(),
|
|
153
|
+
HERMES_OPENAI_RUNTIME: z.enum(['auto', 'codex_app_server']).optional(),
|
|
154
|
+
}).superRefine((env, ctx) => {
|
|
155
|
+
if (env.HEALTH_SNAPSHOT_FULL_FIDELITY_HOURS > env.HEALTH_SNAPSHOT_RETENTION_DAYS * 24) {
|
|
156
|
+
ctx.addIssue({
|
|
157
|
+
code: z.ZodIssueCode.custom,
|
|
158
|
+
path: ['HEALTH_SNAPSHOT_FULL_FIDELITY_HOURS'],
|
|
159
|
+
message: 'HEALTH_SNAPSHOT_FULL_FIDELITY_HOURS cannot exceed HEALTH_SNAPSHOT_RETENTION_DAYS * 24',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (env.NOTIFICATION_READ_RETENTION_DAYS > env.NOTIFICATION_MAX_RETENTION_DAYS) {
|
|
163
|
+
ctx.addIssue({
|
|
164
|
+
code: z.ZodIssueCode.custom,
|
|
165
|
+
path: ['NOTIFICATION_READ_RETENTION_DAYS'],
|
|
166
|
+
message: 'NOTIFICATION_READ_RETENTION_DAYS cannot exceed NOTIFICATION_MAX_RETENTION_DAYS',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (env.IAM_CORE_URL) {
|
|
170
|
+
for (const field of ['IAM_TOKEN_ISSUER', 'IAM_TOKEN_AUDIENCE']) {
|
|
171
|
+
if (env[field])
|
|
172
|
+
continue;
|
|
173
|
+
ctx.addIssue({
|
|
174
|
+
code: z.ZodIssueCode.custom,
|
|
175
|
+
path: [field],
|
|
176
|
+
message: `${field} is required when IAM_CORE_URL is configured`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (env.NODE_ENV === 'production' && env.MAGNUS_SECRET) {
|
|
181
|
+
ctx.addIssue({
|
|
182
|
+
code: z.ZodIssueCode.custom,
|
|
183
|
+
path: ['MAGNUS_SECRET'],
|
|
184
|
+
message: 'MAGNUS_SECRET is retired; configure purpose-specific credentials',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (env.NODE_ENV === 'production'
|
|
188
|
+
&& env.CORS_ORIGIN.split(',').some((origin) => origin.trim() === '*')) {
|
|
189
|
+
ctx.addIssue({
|
|
190
|
+
code: z.ZodIssueCode.custom,
|
|
191
|
+
path: ['CORS_ORIGIN'],
|
|
192
|
+
message: 'CORS_ORIGIN cannot be wildcard in production',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (Boolean(env.TLS_CERT_PATH) !== Boolean(env.TLS_KEY_PATH)) {
|
|
196
|
+
ctx.addIssue({
|
|
197
|
+
code: z.ZodIssueCode.custom,
|
|
198
|
+
path: ['TLS_CERT_PATH'],
|
|
199
|
+
message: 'TLS_CERT_PATH and TLS_KEY_PATH must be configured together',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (env.NODE_ENV === 'production' && (!env.TLS_CERT_PATH || !env.TLS_KEY_PATH)) {
|
|
203
|
+
ctx.addIssue({
|
|
204
|
+
code: z.ZodIssueCode.custom,
|
|
205
|
+
path: ['TLS_CERT_PATH'],
|
|
206
|
+
message: 'TLS_CERT_PATH and TLS_KEY_PATH are required in production',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (env.NODE_ENV === 'production' && env.ALLOW_INSECURE_HTTP) {
|
|
210
|
+
ctx.addIssue({
|
|
211
|
+
code: z.ZodIssueCode.custom,
|
|
212
|
+
path: ['ALLOW_INSECURE_HTTP'],
|
|
213
|
+
message: 'ALLOW_INSECURE_HTTP cannot be enabled in production',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (env.ENABLE_AGENT_DISCOVERY && !env.AGENT_DISCOVERY_SECRET) {
|
|
217
|
+
ctx.addIssue({
|
|
218
|
+
code: z.ZodIssueCode.custom,
|
|
219
|
+
path: ['AGENT_DISCOVERY_SECRET'],
|
|
220
|
+
message: 'AGENT_DISCOVERY_SECRET is required when ENABLE_AGENT_DISCOVERY=true',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (env.SECURITY_FUSION_MODE === 'local' && !env.SECURITY_FUSION_RECEIPT_SIGNING_KEY) {
|
|
224
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['SECURITY_FUSION_RECEIPT_SIGNING_KEY'],
|
|
225
|
+
message: 'SECURITY_FUSION_RECEIPT_SIGNING_KEY is required when SECURITY_FUSION_MODE=local' });
|
|
226
|
+
}
|
|
227
|
+
if (env.MAGNUS_PLUGIN_API_ENABLED) {
|
|
228
|
+
if (!env.MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON)
|
|
229
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON'],
|
|
230
|
+
message: 'MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON is required when MAGNUS_PLUGIN_API_ENABLED=true' });
|
|
231
|
+
if (!env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256)
|
|
232
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256'],
|
|
233
|
+
message: 'MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256 is required when MAGNUS_PLUGIN_API_ENABLED=true' });
|
|
234
|
+
}
|
|
235
|
+
if (env.NODE_ENV === 'production' && !env.DATABASE_URL) {
|
|
236
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['DATABASE_URL'],
|
|
237
|
+
message: 'DATABASE_URL is required in production' });
|
|
238
|
+
}
|
|
239
|
+
if (env.NODE_ENV === 'production' && env.MAGNUS_PLUGIN_TOKEN_SHA256) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: z.ZodIssueCode.custom,
|
|
242
|
+
path: ['MAGNUS_PLUGIN_TOKEN_SHA256'],
|
|
243
|
+
message: 'MAGNUS_PLUGIN_TOKEN_SHA256 is retired; configure per-installation read digests',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256
|
|
247
|
+
&& env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256 === env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256) {
|
|
248
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_SHARED_RESOURCE_TOKEN_SHA256'],
|
|
249
|
+
message: 'shared resource credential must differ from plugin credentials' });
|
|
250
|
+
}
|
|
251
|
+
if (env.MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON) {
|
|
252
|
+
try {
|
|
253
|
+
const digests = JSON.parse(env.MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON);
|
|
254
|
+
const entries = Object.entries(digests);
|
|
255
|
+
if (entries.length < 1 || entries.length > 16)
|
|
256
|
+
throw new Error('plugin digest registry must contain 1-16 entries');
|
|
257
|
+
const seen = new Set();
|
|
258
|
+
for (const [consumer, digest] of entries) {
|
|
259
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(consumer))
|
|
260
|
+
throw new Error('plugin consumer ID is invalid');
|
|
261
|
+
if (typeof digest !== 'string' || !/^[a-f0-9]{64}$/.test(digest))
|
|
262
|
+
throw new Error(`${consumer} digest is invalid`);
|
|
263
|
+
if (seen.has(digest))
|
|
264
|
+
throw new Error('plugin read credentials must be unique per consumer');
|
|
265
|
+
seen.add(digest);
|
|
266
|
+
}
|
|
267
|
+
if (env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256 && seen.has(env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256)) {
|
|
268
|
+
throw new Error('plugin operator credential must differ from every read credential');
|
|
269
|
+
}
|
|
270
|
+
if (env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256 && (seen.has(env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256)
|
|
271
|
+
|| env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256 === env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256)) {
|
|
272
|
+
throw new Error('shared resource credential must differ from plugin credentials');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
ctx.addIssue({
|
|
277
|
+
code: z.ZodIssueCode.custom,
|
|
278
|
+
path: ['MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON'],
|
|
279
|
+
message: error instanceof Error ? error.message : 'Invalid plugin digest registry',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (env.MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON) {
|
|
284
|
+
try {
|
|
285
|
+
const digests = JSON.parse(env.MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON);
|
|
286
|
+
const entries = Object.entries(digests);
|
|
287
|
+
if (entries.length < 1 || entries.length > 32)
|
|
288
|
+
throw new Error('shared resource digest registry must contain 1-32 entries');
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
for (const [consumer, digest] of entries) {
|
|
291
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,95}$/.test(consumer))
|
|
292
|
+
throw new Error('shared resource consumer ID is invalid');
|
|
293
|
+
if (typeof digest !== 'string' || !/^[a-f0-9]{64}$/.test(digest))
|
|
294
|
+
throw new Error(`${consumer} digest is invalid`);
|
|
295
|
+
if (seen.has(digest))
|
|
296
|
+
throw new Error('shared resource credentials must be unique per consumer');
|
|
297
|
+
seen.add(digest);
|
|
298
|
+
}
|
|
299
|
+
if (env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256 && seen.has(env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256))
|
|
300
|
+
throw new Error('legacy and per-client shared resource credentials must differ');
|
|
301
|
+
if (env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256 && seen.has(env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256))
|
|
302
|
+
throw new Error('shared resource credentials must differ from plugin credentials');
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON'],
|
|
306
|
+
message: error instanceof Error ? error.message : 'Invalid shared resource digest registry' });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const otherScopedDigests = new Set([
|
|
310
|
+
env.MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256,
|
|
311
|
+
env.MAGNUS_FLEET_CHECK_TOKEN_SHA256,
|
|
312
|
+
env.MAGNUS_COLLECTOR_TOKEN_SHA256,
|
|
313
|
+
...configuredDigestValues(env.MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON),
|
|
314
|
+
].filter((value) => Boolean(value)));
|
|
315
|
+
if (env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256 && otherScopedDigests.has(env.MAGNUS_SHARED_RESOURCE_TOKEN_SHA256)) {
|
|
316
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_SHARED_RESOURCE_TOKEN_SHA256'],
|
|
317
|
+
message: 'legacy shared resource credential must differ from every plugin, fleet, and collector credential' });
|
|
318
|
+
}
|
|
319
|
+
if (configuredDigestValues(env.MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON).some(digest => otherScopedDigests.has(digest))) {
|
|
320
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['MAGNUS_SHARED_RESOURCE_TOKEN_DIGESTS_JSON'],
|
|
321
|
+
message: 'shared resource credentials must differ from every plugin, fleet, and collector credential' });
|
|
322
|
+
}
|
|
323
|
+
for (const raw of env.WEBHOOK_ALLOWED_ORIGINS.split(',').map((value) => value.trim()).filter(Boolean)) {
|
|
324
|
+
try {
|
|
325
|
+
const url = new URL(raw);
|
|
326
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash
|
|
327
|
+
|| (url.pathname !== '/' && url.pathname !== ''))
|
|
328
|
+
throw new Error('invalid');
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
ctx.addIssue({
|
|
332
|
+
code: z.ZodIssueCode.custom,
|
|
333
|
+
path: ['WEBHOOK_ALLOWED_ORIGINS'],
|
|
334
|
+
message: 'WEBHOOK_ALLOWED_ORIGINS must contain comma-separated exact HTTPS origins',
|
|
335
|
+
});
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const purposeSecrets = [
|
|
340
|
+
'API_SECRET',
|
|
341
|
+
'JWT_SECRET',
|
|
342
|
+
'NODE_MANAGER_SECRET',
|
|
343
|
+
'AGENT_DISCOVERY_SECRET',
|
|
344
|
+
'CREDENTIAL_ROTATION_KEY',
|
|
345
|
+
'AUDIT_HMAC_SECRET',
|
|
346
|
+
'RESULT_SIGNING_SECRET',
|
|
347
|
+
'RESULT_SIGNING_PREVIOUS_SECRET',
|
|
348
|
+
'LOG_INGEST_SIGNING_SECRET',
|
|
349
|
+
'LOG_INGEST_PREVIOUS_SIGNING_SECRET',
|
|
350
|
+
'SECURITY_FUSION_RECEIPT_SIGNING_KEY',
|
|
351
|
+
'METRICS_TOKEN',
|
|
352
|
+
'WEBHOOK_ENCRYPTION_KEY',
|
|
353
|
+
'RESOURCE_ENROLLMENT_DASHBOARD_SECRET',
|
|
354
|
+
'SECURITY_APPROVAL_BRIDGE_TOKEN',
|
|
355
|
+
'COMMAND_RECONCILIATION_SECRET',
|
|
356
|
+
'RESOURCE_PARTICIPANT_RECONCILIATION_SECRET',
|
|
357
|
+
'SHARED_RESOURCE_RECONCILIATION_SECRET',
|
|
358
|
+
];
|
|
359
|
+
const configured = new Map();
|
|
360
|
+
for (const field of purposeSecrets) {
|
|
361
|
+
const value = env[field];
|
|
362
|
+
if (!value)
|
|
363
|
+
continue;
|
|
364
|
+
const prior = configured.get(value);
|
|
365
|
+
if (prior) {
|
|
366
|
+
ctx.addIssue({
|
|
367
|
+
code: z.ZodIssueCode.custom,
|
|
368
|
+
path: [field],
|
|
369
|
+
message: `${field} must be distinct from ${prior}`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
configured.set(value, field);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}).transform((env) => {
|
|
377
|
+
const magnusRouterUrl = env.MAGNUS_ROUTER_URL ?? env.HIVE_ROUTER_URL;
|
|
378
|
+
const magnusRouterModel = env.MAGNUS_ROUTER_MODEL ?? env.HIVE_ROUTER_MODEL;
|
|
379
|
+
const magnusRouterApiKey = env.MAGNUS_ROUTER_API_KEY ?? env.HIVE_ROUTER_API_KEY;
|
|
380
|
+
return {
|
|
381
|
+
...env,
|
|
382
|
+
MAGNUS_ROUTER_URL: magnusRouterUrl,
|
|
383
|
+
MAGNUS_ROUTER_MODEL: magnusRouterModel,
|
|
384
|
+
MAGNUS_ROUTER_API_KEY: magnusRouterApiKey,
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
/** Parse and validate env vars. Throws on invalid config. */
|
|
388
|
+
export function loadEnv(overrides) {
|
|
389
|
+
const source = { ...process.env, ...overrides };
|
|
390
|
+
return envSchema.parse(source);
|
|
391
|
+
}
|
|
392
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const truthy = (value) => (value ?? '').trim().toLowerCase() === 'true';
|
|
2
|
+
export function featureFlags(env = process.env) {
|
|
3
|
+
const mode = env['SECURITY_FUSION_MODE'];
|
|
4
|
+
return {
|
|
5
|
+
iam: Boolean(env['IAM_CORE_URL']),
|
|
6
|
+
router: Boolean(env['MAGNUS_ROUTER_URL'] || env['HIVE_ROUTER_URL']),
|
|
7
|
+
hivemem: Boolean(env['HIVEMEM_URL']),
|
|
8
|
+
securityFusion: mode === 'local' || mode === 'remote' ? mode : 'off',
|
|
9
|
+
securityConsoleUrl: env['MAGNUS_SECURITY_CONSOLE_URL'] || null,
|
|
10
|
+
pluginApi: truthy(env['MAGNUS_PLUGIN_API_ENABLED']),
|
|
11
|
+
hermesRegistry: truthy(env['MAGNUS_HERMES_REGISTRY_ENABLED']),
|
|
12
|
+
resourceLeases: truthy(env['MAGNUS_RESOURCE_LEASES_ENABLED']),
|
|
13
|
+
agentDiscovery: truthy(env['ENABLE_AGENT_DISCOVERY']),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=features.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function isLoopbackHost(hostname) {
|
|
2
|
+
const normalized = hostname.trim().toLowerCase();
|
|
3
|
+
return normalized === '127.0.0.1' || normalized === '::1' || normalized === 'localhost';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the listener mode without silently downgrading to cleartext.
|
|
7
|
+
*
|
|
8
|
+
* TLS listeners retain the historical all-interface default. Cleartext is a
|
|
9
|
+
* development-only escape hatch and is always confined to loopback.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveListenerSecurity(options) {
|
|
12
|
+
const certConfigured = Boolean(options.certPath);
|
|
13
|
+
const keyConfigured = Boolean(options.keyPath);
|
|
14
|
+
if (certConfigured !== keyConfigured) {
|
|
15
|
+
throw new Error('TLS_CERT_PATH and TLS_KEY_PATH must be configured together');
|
|
16
|
+
}
|
|
17
|
+
if (certConfigured && keyConfigured) {
|
|
18
|
+
if (!options.pathExists(options.certPath) || !options.pathExists(options.keyPath)) {
|
|
19
|
+
throw new Error('Configured TLS certificate or key does not exist');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
hostname: options.bindHost?.trim() || '0.0.0.0',
|
|
23
|
+
useTls: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (options.nodeEnv === 'production') {
|
|
27
|
+
throw new Error('TLS_CERT_PATH and TLS_KEY_PATH are required in production');
|
|
28
|
+
}
|
|
29
|
+
if (!options.allowInsecureHttp) {
|
|
30
|
+
throw new Error('Cleartext HTTP requires explicit ALLOW_INSECURE_HTTP=true');
|
|
31
|
+
}
|
|
32
|
+
const hostname = options.bindHost?.trim() || '127.0.0.1';
|
|
33
|
+
if (!isLoopbackHost(hostname)) {
|
|
34
|
+
throw new Error('Cleartext HTTP may bind only to a loopback host');
|
|
35
|
+
}
|
|
36
|
+
return { hostname, useTls: false };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=listener-security.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ─── Canonical version constants — read from package.json ───
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
function readVersion(pkgPath) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = readFileSync(pkgPath, 'utf-8');
|
|
8
|
+
return JSON.parse(raw).version;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return '0.0.0';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
/**
|
|
16
|
+
* Agent version bundled with this Control API deployment.
|
|
17
|
+
*/
|
|
18
|
+
export const AGENT_VERSION = (() => {
|
|
19
|
+
const fromJson = readVersion(resolve(__dirname, '../../agent-version.json'));
|
|
20
|
+
if (fromJson !== '0.0.0')
|
|
21
|
+
return fromJson;
|
|
22
|
+
return readVersion(resolve(__dirname, '../../../../packages/agent/package.json'));
|
|
23
|
+
})();
|
|
24
|
+
/**
|
|
25
|
+
* Current control-api version — read from this repo's own package.json.
|
|
26
|
+
* Used by the self-updater to detect when a `git pull` brings a new version.
|
|
27
|
+
*/
|
|
28
|
+
export const CONTROL_API_VERSION = readVersion(resolve(__dirname, '../../package.json'));
|
|
29
|
+
/** Compiled marker for the fail-closed deployment and rollback contract. */
|
|
30
|
+
export const CONTROL_API_RELEASE_CONTRACT_VERSION = 1;
|
|
31
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PrismaClient } from './generated/prisma/client.js';
|
|
2
|
+
import { PrismaPg } from '@prisma/adapter-pg';
|
|
3
|
+
/**
|
|
4
|
+
* Development-only fallback: a local PostgreSQL database named `magnus`
|
|
5
|
+
* reached as role `magnus` with no password (trust/peer auth). Production
|
|
6
|
+
* (`NODE_ENV=production`) always requires DATABASE_URL.
|
|
7
|
+
*/
|
|
8
|
+
export const DEVELOPMENT_DATABASE_URL = 'postgresql://magnus@127.0.0.1:5432/magnus';
|
|
9
|
+
/** Resolve the PostgreSQL connection string, failing fast in production. */
|
|
10
|
+
export function resolveDatabaseUrl(env = process.env) {
|
|
11
|
+
const configured = env['DATABASE_URL']?.trim();
|
|
12
|
+
if (configured)
|
|
13
|
+
return configured;
|
|
14
|
+
if (env['NODE_ENV'] === 'production')
|
|
15
|
+
throw new Error('DATABASE_URL is required when NODE_ENV=production');
|
|
16
|
+
return DEVELOPMENT_DATABASE_URL;
|
|
17
|
+
}
|
|
18
|
+
let prisma;
|
|
19
|
+
/** Singleton Prisma client — reuses connection across hot-reloads in dev. */
|
|
20
|
+
export function getDb() {
|
|
21
|
+
if (!prisma) {
|
|
22
|
+
const adapter = new PrismaPg({
|
|
23
|
+
connectionString: resolveDatabaseUrl(),
|
|
24
|
+
connectionTimeoutMillis: 5_000,
|
|
25
|
+
idleTimeoutMillis: 30_000,
|
|
26
|
+
max: Number(process.env['DATABASE_POOL_SIZE'] ?? 20),
|
|
27
|
+
});
|
|
28
|
+
prisma = new PrismaClient({
|
|
29
|
+
adapter,
|
|
30
|
+
log: process.env['NODE_ENV'] === 'development'
|
|
31
|
+
? ['query', 'warn', 'error']
|
|
32
|
+
: ['warn', 'error'],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return prisma;
|
|
36
|
+
}
|
|
37
|
+
export async function disconnectDb() {
|
|
38
|
+
if (prisma) {
|
|
39
|
+
await prisma.$disconnect();
|
|
40
|
+
prisma = undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export { PrismaClient };
|
|
44
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// biome-ignore-all lint: generated file
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
/*
|
|
6
|
+
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
|
7
|
+
* Use it to get access to models, enums, and input types.
|
|
8
|
+
*
|
|
9
|
+
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
|
10
|
+
* See `client.ts` for the standard, server-side entry point.
|
|
11
|
+
*
|
|
12
|
+
* 🟢 You can import this file directly.
|
|
13
|
+
*/
|
|
14
|
+
import * as Prisma from './internal/prismaNamespaceBrowser.js';
|
|
15
|
+
export { Prisma };
|
|
16
|
+
export * as $Enums from './enums.js';
|
|
17
|
+
export * from './enums.js';
|
|
18
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// biome-ignore-all lint: generated file
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
/*
|
|
6
|
+
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
|
7
|
+
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
|
8
|
+
*
|
|
9
|
+
* 🟢 You can import this file directly.
|
|
10
|
+
*/
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
import * as $Class from "./internal/class.js";
|
|
15
|
+
import * as Prisma from "./internal/prismaNamespace.js";
|
|
16
|
+
export * as $Enums from './enums.js';
|
|
17
|
+
export * from "./enums.js";
|
|
18
|
+
/**
|
|
19
|
+
* ## Prisma Client
|
|
20
|
+
*
|
|
21
|
+
* Type-safe database client for TypeScript
|
|
22
|
+
* @example
|
|
23
|
+
* ```
|
|
24
|
+
* const prisma = new PrismaClient({
|
|
25
|
+
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
|
|
26
|
+
* })
|
|
27
|
+
* // Fetch zero or more MagnusReleaseCanaries
|
|
28
|
+
* const magnusReleaseCanaries = await prisma.magnusReleaseCanary.findMany()
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Read more in our [docs](https://pris.ly/d/client).
|
|
32
|
+
*/
|
|
33
|
+
export const PrismaClient = $Class.getPrismaClientClass();
|
|
34
|
+
export { Prisma };
|
|
35
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// biome-ignore-all lint: generated file
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
/*
|
|
6
|
+
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
|
7
|
+
*
|
|
8
|
+
* 🟢 You can import this file directly.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=commonInputTypes.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// biome-ignore-all lint: generated file
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
/*
|
|
6
|
+
* This file exports all enum related types from the schema.
|
|
7
|
+
*
|
|
8
|
+
* 🟢 You can import this file directly.
|
|
9
|
+
*/
|
|
10
|
+
export const UserRole = {
|
|
11
|
+
ADMIN: 'ADMIN',
|
|
12
|
+
OPERATOR: 'OPERATOR',
|
|
13
|
+
VIEWER: 'VIEWER'
|
|
14
|
+
};
|
|
15
|
+
export const NodeStatus = {
|
|
16
|
+
ONLINE: 'ONLINE',
|
|
17
|
+
OFFLINE: 'OFFLINE',
|
|
18
|
+
DEGRADED: 'DEGRADED',
|
|
19
|
+
PROVISIONING: 'PROVISIONING',
|
|
20
|
+
DECOMMISSIONED: 'DECOMMISSIONED'
|
|
21
|
+
};
|
|
22
|
+
export const AgentStatus = {
|
|
23
|
+
RUNNING: 'RUNNING',
|
|
24
|
+
STOPPED: 'STOPPED',
|
|
25
|
+
UPDATING: 'UPDATING',
|
|
26
|
+
ERROR: 'ERROR'
|
|
27
|
+
};
|
|
28
|
+
export const CommandStatus = {
|
|
29
|
+
PENDING: 'PENDING',
|
|
30
|
+
QUEUED: 'QUEUED',
|
|
31
|
+
RUNNING: 'RUNNING',
|
|
32
|
+
COMPLETED: 'COMPLETED',
|
|
33
|
+
FAILED: 'FAILED',
|
|
34
|
+
TIMEOUT: 'TIMEOUT',
|
|
35
|
+
CANCELLED: 'CANCELLED'
|
|
36
|
+
};
|
|
37
|
+
export const CronJobStatus = {
|
|
38
|
+
ACTIVE: 'ACTIVE',
|
|
39
|
+
PAUSED: 'PAUSED',
|
|
40
|
+
ERROR: 'ERROR'
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=enums.js.map
|