defmodule OpenAgents.RuntimeConfigTest do
use ExUnit.Case, async: true
alias OpenAgents.RuntimeConfig
alias OpenAgents.Tools.Snapshot
test "the test runtime is valid and produces a content-free report" do
config = RuntimeConfig.load!()
report = RuntimeConfig.readiness_report(config)
assert report["status"] == "ready"
assert report["environment"] == "test"
assert report["groups"] == %{
"cluster" => "ready",
"database" => "ready",
"endpoint" => "ready",
"features" => "ready",
"forge" => "ready",
"github" => "ready",
"providers" => "ready"
}
assert is_boolean(report["features"]["voice"])
assert is_boolean(report["features"]["forge_deploy"])
end
test "a Gate 5 staging profile is accepted and its secrets never enter the report" do
secret = "openai-readiness-secret-sentinel"
oauth_secret = "oauth-readiness-secret-sentinel"
settings =
staging_settings()
|> Map.put(:openai_api_key, secret)
|> update_oauth(:client_secret, oauth_secret)
config = RuntimeConfig.load!(settings)
encoded = config |> RuntimeConfig.readiness_report() |> Jason.encode!()
assert config.environment == :staging
assert config.staging_gate == 5
refute encoded =~ secret
refute encoded =~ oauth_secret
refute encoded =~ "staging.openagents.com"
refute encoded =~ "ecto://"
end
test "GitHub sign-in accepts only the email scope" do
assert {:error, %{setting: :github_oauth_scopes}} =
staging_settings()
|> Map.put(:github_oauth_scopes, ["repo", "read:org"])
|> RuntimeConfig.validate()
assert {:ok, _config} = RuntimeConfig.validate(staging_settings())
end
test "staging gates refuse features before their admission gate" do
settings = staging_settings() |> put_nested(:voice, :enabled, true)
assert {:error, %{setting: :staging_gate}} = RuntimeConfig.validate(settings)
end
test "Gate 12 requires exact packaged source and image identities" do
settings = staging_settings() |> Map.put(:staging_gate, 12)
assert {:error, %{setting: :build_revision}} = RuntimeConfig.validate(settings)
settings = Map.put(settings, :build_revision, String.duplicate("a", 40))
assert {:error, %{setting: :image_digest}} = RuntimeConfig.validate(settings)
assert {:ok, _config} =
settings
|> Map.put(:image_digest, "sha256:" <> String.duplicate("b", 64))
|> RuntimeConfig.validate()
end
test "staging cleanup is admitted only at Gate 12 or later" do
exact_identity = %{
build_revision: String.duplicate("a", 40),
image_digest: "sha256:" <> String.duplicate("b", 64)
}
assert {:ok, _config} =
staging_settings()
|> Map.merge(exact_identity)
|> Map.merge(%{staging_gate: 12, staging_cleanup_enabled: true})
|> RuntimeConfig.validate()
assert {:error, %{setting: :staging_cleanup_enabled}} =
staging_settings()
|> Map.merge(exact_identity)
|> Map.merge(%{staging_gate: 11, staging_cleanup_enabled: true})
|> RuntimeConfig.validate()
assert {:error, %{setting: :staging_cleanup_enabled}} =
staging_settings()
|> Map.put(:staging_cleanup_enabled, "true")
|> RuntimeConfig.validate()
end
test "fleet deployment requires the isolated GCP rolling provider" do
settings =
staging_settings()
|> Map.merge(%{
staging_gate: 13,
build_revision: String.duplicate("a", 40),
image_digest: "sha256:" <> String.duplicate("b", 64),
forge_enabled: true,
forge_deploy_lane_enabled: true,
forge_boot_converge_enabled: true,
forge_expected_fleet_size: 3,
forge_operator_token: "staging-operator-token",
forge_rolling_provider: OpenAgents.Forge.RollingProvider.Gcp,
forge_wal_dir: "/var/lib/openagents/forge-wal",
ra_enabled: true,
dns_cluster_query: "openagents-fleet.staging.internal",
distribution: [
enabled: true,
node_configured: true,
cookie_configured: true,
port_min: 9_100,
port_max: 9_115
]
})
|> Map.put(OpenAgents.Forge.RollingProvider.Gcp, rolling_gcp_config())
assert {:ok, _config} = RuntimeConfig.validate(settings)
assert {:error, %{setting: :forge_rolling_provider}} =
settings
|> Map.put(
OpenAgents.Forge.RollingProvider.Gcp,
Keyword.put(rolling_gcp_config(), :project_id, "production-project")
)
|> RuntimeConfig.validate()
end
test "production admits direct deployment without an automatic rolling provider" do
settings =
staging_settings()
|> Map.merge(%{
runtime_environment: :production,
staging_gate: 16,
production_deploy_enabled: true,
build_revision: String.duplicate("a", 40),
image_digest: "sha256:" <> String.duplicate("b", 64),
forge_enabled: true,
forge_deploy_lane_enabled: true,
forge_boot_converge_enabled: false,
forge_expected_fleet_size: 3,
forge_operator_token: "production-operator-token",
forge_rolling_provider: nil,
forge_wal_dir: "/var/lib/openagents/forge-wal",
github_token_encryption_key_id: "production-2026-08",
ra_enabled: true,
dns_cluster_query: "openagents.fleet.internal",
distribution: [
enabled: true,
node_configured: true,
cookie_configured: true,
port_min: 9_100,
port_max: 9_115
]
})
|> Map.put(OpenAgentsWeb.Endpoint,
url: [host: "openagents.com", port: 443, scheme: "https"],
check_origin: ["https://openagents.com"]
)
|> update_oauth(:redirect_uri, "https://openagents.com/auth/github/callback")
assert {:ok, _config} = RuntimeConfig.validate(settings)
end
test "staging rejects an insecure allowed origin" do
settings =
staging_settings()
|> put_endpoint(:check_origin, ["https://staging.openagents.com", "http://other.example"])
assert {:error, %{setting: :allowed_origins}} = RuntimeConfig.validate(settings)
end
test "staging rejects a path-bearing allowed origin" do
settings =
staging_settings()
|> put_endpoint(:check_origin, [
"https://staging.openagents.com",
"https://other.example/path"
])
assert {:error, %{setting: :allowed_origins}} = RuntimeConfig.validate(settings)
end
test "staging rejects allowed origins with query, fragment, or missing scheme" do
for origin <- [
"https://other.example?query=1",
"https://other.example#fragment",
"https://",
"other.example"
] do
settings =
staging_settings()
|> put_endpoint(:check_origin, ["https://staging.openagents.com", origin])
assert {:error, %{setting: :allowed_origins}} = RuntimeConfig.validate(settings),
"expected #{origin} to be rejected"
end
end
test "enabled OpenAI features require the centralized provider secret" do
settings =
staging_settings()
|> Map.put(:staging_gate, 14)
|> put_nested(:voice, :enabled, true)
|> Map.put(:openai_api_key, nil)
assert {:error, %{setting: :openai_api_key}} = RuntimeConfig.validate(settings)
end
test "recording cannot start without its encryption key" do
settings =
staging_settings()
|> Map.put(:staging_gate, 14)
|> put_nested(:voice, :enabled, true)
|> put_nested(:voice_recording, :enabled, true)
|> Map.put(:voice_recording_encryption_key, nil)
assert {:error, %{setting: :voice_recording_encryption_key}} =
RuntimeConfig.validate(settings)
end
test "a release refuses to boot without the content vault's own key" do
# VAULT-001, issue #193. Nothing bridges to this key, so an unset value has
# to stop the boot: the alternative is a node that accepts a project note
# or a voice transcript and then cannot seal it.
settings = Map.put(staging_settings(), :content_encryption_key, nil)
assert {:error, %{setting: :content_encryption_key}} = RuntimeConfig.validate(settings)
borrowed =
Map.put(
staging_settings(),
:content_encryption_key,
"not-a-base64-encoded-32-byte-key"
)
assert {:error, %{setting: :content_encryption_key}} = RuntimeConfig.validate(borrowed)
end
test "hot-load examples are executable startup policy, not prose" do
settings =
staging_settings()
|> Map.put(:forge_hot_load_examples, %{
"OpenAgentsWeb.ChatLive" => false,
"OpenAgents.Accounts" => false
})
assert {:error, %{setting: :forge_hot_load_examples}} = RuntimeConfig.validate(settings)
end
test "production remains locked until an explicit later decision" do
settings = staging_settings() |> Map.put(:runtime_environment, :production)
assert {:error, %{setting: :production_deploy_enabled}} = RuntimeConfig.validate(settings)
end
test "validation diagnostics name settings without echoing their values" do
sentinel = "secret-host-value.invalid/path"
settings = staging_settings() |> put_endpoint(:url, host: sentinel)
error =
assert_raise ArgumentError, fn ->
RuntimeConfig.load!(settings)
end
assert error.message =~ "endpoint_host"
refute error.message =~ sentinel
end
test "GitHub keyring metadata and prior keys fail closed without entering readiness" do
sentinel = Base.encode64(:crypto.strong_rand_bytes(32))
settings =
staging_settings()
|> Map.put(:github_token_decryption_keys, %{"staging-prior-key" => sentinel})
encoded =
settings |> RuntimeConfig.load!() |> RuntimeConfig.readiness_report() |> Jason.encode!()
refute encoded =~ sentinel
refute encoded =~ "staging-prior-key"
assert {:error, %{setting: :github_token_encryption_key_id}} =
settings
|> Map.put(:github_token_encryption_key_id, "production-wrong-environment")
|> RuntimeConfig.validate()
assert {:error, %{setting: :github_token_decryption_keys}} =
settings
|> Map.put(:github_token_decryption_keys, %{"prior" => "not-base64"})
|> RuntimeConfig.validate()
assert {:error, %{setting: :github_token_decryption_keys}} =
settings
|> Map.put(:github_token_decryption_keys, %{"production-prior" => sentinel})
|> RuntimeConfig.validate()
assert {:error, %{setting: :github_token_decryption_keys}} =
settings
|> Map.put(:github_token_decryption_keys, %{"staging-2026-08" => sentinel})
|> RuntimeConfig.validate()
end
test "the machine pairing vault key is required and named on its own" do
settings = staging_settings() |> Map.put(:machine_token_encryption_key, nil)
assert {:error, %{setting: :machine_token_encryption_key}} =
RuntimeConfig.validate(settings)
end
test "a staging or production release refuses to serve on a borrowed vault key" do
# VAULT-001, issues #192 and #253. The presence check above passes for a
# bridged boot, because `config/runtime.exs` hands the pairing vault the
# GitHub vault's key, which is a valid 32-byte key. Distinctness is what
# separates a provisioned key from a load-bearing bridge.
settings = staging_settings()
github_key = Map.fetch!(settings, :github_token_encryption_key)
assert {:ok, _config} = RuntimeConfig.validate(settings)
bridged = Map.put(settings, :machine_token_encryption_key, github_key)
assert {:error, %{setting: :machine_token_encryption_key, reason: reason}} =
RuntimeConfig.validate(bridged)
assert reason =~ "github_token_encryption_key"
# The same rule holds for every other vault, and names the borrower.
assert {:error, %{setting: :content_encryption_key}} =
settings
|> Map.put(:content_encryption_key, github_key)
|> RuntimeConfig.validate()
assert {:error, %{setting: :voice_recording_encryption_key}} =
settings
|> Map.put(
:voice_recording_encryption_key,
Map.fetch!(settings, :machine_token_encryption_key)
)
|> RuntimeConfig.validate()
# Development boots the bridge without complaint: a shared key there
# strands nothing a person would retry.
assert {:ok, _config} =
bridged
|> Map.put(:runtime_environment, :development)
|> Map.put(:migrate_on_boot, false)
|> Map.put(:secure_cookies, false)
|> update_oauth(:redirect_uri, "http://localhost:4000/auth/github/callback")
|> Map.put(:github_token_encryption_key_id, "development-2026-08")
|> RuntimeConfig.validate()
end
test "forge mirror remotes refuse credential-bearing URLs" do
for url <- [
"https://operator:secret@mirror.example/openagents.com.git",
"ssh://operator:secret@mirror.example/openagents.com.git",
"operator:secret@mirror.example:openagents.com.git"
] do
settings =
staging_settings()
|> Map.put(:forge_mirror_urls, %{"openagents.com" => url})
assert {:error, %{setting: :forge_mirror_urls}} = RuntimeConfig.validate(settings)
end
assert {:ok, _config} =
staging_settings()
|> Map.put(:forge_mirror_urls, %{"openagents.com" => "/var/lib/openagents/mirror"})
|> RuntimeConfig.validate()
assert {:ok, _config} =
staging_settings()
|> Map.put(:forge_mirror_urls, %{
"openagents.com" => "ssh://git@github.com/OpenAgentsInc/openagents.com.git"
})
|> RuntimeConfig.validate()
end
test "startup refuses an empty tool catalog when tools are enabled" do
config = RuntimeConfig.load!(staging_settings())
snapshot = %Snapshot{
schema: "test",
digest: "test",
tools: %{},
all_tools: %{},
modules: %{}
}
assert_raise ArgumentError, ~r/tools catalog is empty/, fn ->
RuntimeConfig.verify_startup!(config, snapshot)
end
end
test "an SCV lane refuses a clone root on the container layer" do
# Gate 14 admits the lane; the clone root is what decides whether the
# repository lands on the durable volume or on the boot disk.
settings =
staging_settings()
|> Map.put(:staging_gate, 14)
|> Map.put(:build_revision, String.duplicate("a", 40))
|> Map.put(:image_digest, "sha256:" <> String.duplicate("b", 64))
|> put_nested(:work, :enabled, true)
|> Map.put(:work_workers_enabled, true)
|> put_nested(:scv_deploy, :enabled, true)
assert {:error, %{setting: :scv_temporary_root}} =
settings
|> put_nested(:scv_codex, :temporary_root, "/tmp")
|> RuntimeConfig.validate()
assert {:error, %{setting: :scv_temporary_root}} =
settings
|> put_nested(:scv_codex, :temporary_root, "/tmp/openagents-scv")
|> RuntimeConfig.validate()
assert {:ok, config} =
settings
|> put_nested(:scv_codex, :temporary_root, "/var/lib/openagents/workspace/scv")
|> RuntimeConfig.validate()
assert RuntimeConfig.feature_enabled?(config, :scv_deploy)
end
test "the deployment control plane worker is off unless configuration enables it" do
# DEPLOYPLANE-005: the API records and evaluates runs on any host, but only
# a host whose configuration admits the feature may claim and execute one.
assert {:ok, disabled} = RuntimeConfig.validate(staging_settings())
refute RuntimeConfig.feature_enabled?(disabled, :deployment_control_plane)
assert {:ok, enabled} =
staging_settings()
|> Map.put(:deployment_control_plane_enabled, true)
|> RuntimeConfig.validate()
assert RuntimeConfig.feature_enabled?(enabled, :deployment_control_plane)
assert {:error, %{setting: :deployment_control_plane_enabled}} =
staging_settings()
|> Map.put(:deployment_control_plane_enabled, "yes")
|> RuntimeConfig.validate()
end
defp staging_settings do
current = Map.new(Application.get_all_env(:openagents))
current
|> Map.merge(%{
runtime_environment: :staging,
staging_gate: 5,
production_deploy_enabled: false,
secure_cookies: true,
https_aliases: [],
migrate_on_boot: true,
provider: OpenAgents.Providers.OpenAI,
openai_api_key: "staging-openai-secret",
forge_enabled: false,
forge_deploy_lane_enabled: false,
forge_boot_converge_enabled: false,
turn_recovery_enabled: false,
voice_retention_enabled: false,
deployment_control_plane_enabled: false,
computer_controller_enabled: false,
ra_enabled: false,
forge_repos: ["openagents.com"],
forge_repo_owners: %{"openagents.com" => "OpenAgentsInc"},
forge_public_visibility: %{"openagents.com" => :l3},
forge_public_paths: %{"openagents.com" => []},
forge_operator_token: nil,
github_token_encryption_key_id: "staging-2026-08",
dns_cluster_query: nil,
distribution: [
enabled: false,
node_configured: false,
cookie_configured: false,
port_min: 9_100,
port_max: 9_115
]
})
|> Map.put(OpenAgents.Repo,
url: "ecto://runtime-user:runtime-password@database/openagents",
pool_size: 10
)
|> Map.put(OpenAgentsWeb.Endpoint,
url: [host: "staging.openagents.com", port: 443, scheme: "https"],
check_origin: ["https://staging.openagents.com"]
)
|> update_oauth(:redirect_uri, "https://staging.openagents.com/auth/github/callback")
|> put_nested(:voice, :enabled, false)
|> put_nested(:voice_recording, :enabled, false)
|> put_nested(:work, :enabled, false)
|> put_nested(:semantic_index, :enabled, false)
|> put_nested(:experience_memory, :enabled, false)
|> put_nested(:graph_memory, :enabled, false)
|> put_nested(:memory_portability, :enabled, false)
|> put_nested(:shadow_programs, :enabled, false)
|> put_nested(:scv_codex, :enabled, false)
|> put_nested(:scv_codex, :temporary_root, "/var/lib/openagents/workspace/scv")
end
defp rolling_gcp_config do
[
project_id: "staging-project",
production_project_id: "production-project",
zone: "us-central1-a",
instances: %{
"openagents@fleet-1.staging.internal" => "openagents-fleet-1",
"openagents@fleet-2.staging.internal" => "openagents-fleet-2",
"openagents@fleet-3.staging.internal" => "openagents-fleet-3"
},
image_repository: "us-central1-docker.pkg.dev/staging-project/openagents/app",
deployer_node: :"openagents-deployer@openagents-deployer.staging.internal"
]
end
defp update_oauth(settings, key, value) do
Map.update!(settings, :github_oauth, &Keyword.put(&1, key, value))
end
defp put_nested(settings, group, key, value) do
Map.update!(settings, group, &Keyword.put(&1, key, value))
end
defp put_endpoint(settings, key, value) do
Map.update!(settings, OpenAgentsWeb.Endpoint, &Keyword.put(&1, key, value))
end
describe "internal_surfaces_visible?/1" do
test "production does not advertise the component library" do
refute RuntimeConfig.internal_surfaces_visible?(%RuntimeConfig{
environment: :production,
staging_gate: 0,
features: %{},
groups: %{}
})
end
test "every other environment does" do
for environment <- [:development, :test, :staging] do
assert RuntimeConfig.internal_surfaces_visible?(%RuntimeConfig{
environment: environment,
staging_gate: 0,
features: %{},
groups: %{}
}),
"expected #{environment} to advertise the component library"
end
end
end
end