Summary
ConfigLoader builds its SecretResolver with projectRoot: getDirectoryFromPath(arguments.path) (cli/lucli/services/deploy/config/ConfigLoader.cfc:55). For the standard layout (config/deploy.yml), ${VAR} tokens are resolved against config/.kamal/secrets — a file nothing creates — instead of the project-root .kamal/secrets that wheels deploy init scaffolds and that every other deploy code path (DeploySecretsCli, registry login via new SecretResolver() defaulting to expandPath("./")) uses.
Repro (wheels 4.0.3 and develop)
printf 'APP_NAME=fromsecrets\n' > .kamal/secrets
printf 'service: ${APP_NAME}\nimage: x/y\nservers:\n - 192.0.2.10\n' > config/deploy.yml
wheels deploy config # SERVICE: '' ← secrets leg never fired
mkdir -p config/.kamal && cp .kamal/secrets config/.kamal/secrets
wheels deploy config # SERVICE resolves ← from the wrong location
Process-env fallback works (APP_NAME=fromenv wheels deploy config → fromenv), which masks the bug until someone relies on the documented lookup order — config-reference.mdx, architecture.mdx, and migrating-from-kamal.mdx all document .kamal/secrets as step 2 of the interpolation chain.
Root cause
ConfigLoader.cfc:55 derives the secrets root from the YAML file's own directory instead of the project root.
Proposed direction
Resolve the secrets root as the parent of the config directory when the config path ends in config/deploy.yml — or better, accept an explicit projectRoot from the verb layer (Module.cfc knows the CWD) instead of deriving it from the YAML path.
Acceptance
- Root-level
.kamal/secrets feeds ${VAR} interpolation for the standard config/deploy.yml layout (spec asserting $resolveVar sees it).
- The DeploySecretsCli / registry-login resolution and the interpolation resolution agree on one root.
Reported by the guide-behavioral-audit P1 batch 2 (work item p1-16-deploy, claim arch-04; wheels CLI 4.0.3 + develop source).
Summary
ConfigLoader builds its SecretResolver with
projectRoot: getDirectoryFromPath(arguments.path)(cli/lucli/services/deploy/config/ConfigLoader.cfc:55). For the standard layout (config/deploy.yml),${VAR}tokens are resolved againstconfig/.kamal/secrets— a file nothing creates — instead of the project-root.kamal/secretsthatwheels deploy initscaffolds and that every other deploy code path (DeploySecretsCli, registry login vianew SecretResolver()defaulting toexpandPath("./")) uses.Repro (wheels 4.0.3 and develop)
Process-env fallback works (
APP_NAME=fromenv wheels deploy config→ fromenv), which masks the bug until someone relies on the documented lookup order — config-reference.mdx, architecture.mdx, and migrating-from-kamal.mdx all document.kamal/secretsas step 2 of the interpolation chain.Root cause
ConfigLoader.cfc:55derives the secrets root from the YAML file's own directory instead of the project root.Proposed direction
Resolve the secrets root as the parent of the config directory when the config path ends in
config/deploy.yml— or better, accept an explicit projectRoot from the verb layer (Module.cfc knows the CWD) instead of deriving it from the YAML path.Acceptance
.kamal/secretsfeeds${VAR}interpolation for the standardconfig/deploy.ymllayout (spec asserting$resolveVarsees it).Reported by the guide-behavioral-audit P1 batch 2 (work item p1-16-deploy, claim arch-04; wheels CLI 4.0.3 + develop source).