Skip to content

First-class variables + secret-substitution layer in build invocations #213

Description

@ChrisonSimtian

Context

We have basic secrets today (fallout :secrets, v1:-encrypted values in parameters.json — see #212 for the audit). It's a storage mechanism: put secrets in the file, the build reads them via [Parameter] [Secret] readonly string ApiKey;.

We don't have a first-class variable layer — non-secret named values that can be:

  • Defined once per repo (or per profile / environment) in a structured way.
  • Substituted into other parameter values, tool arguments, and CI config generators — e.g. apiUrl = "https://${env}.example.com/api" where ${env} resolves from a variable.
  • Sourced from layered config (build defaults → repo profile → environment override → CLI arg), with clear precedence.

Variables and secrets go together:

  • Both are named values configured per repo/profile/environment.
  • Both want the same substitution syntax (${name} in tool args, CI config, artifact paths).
  • Same resolution order: command-line > env var > profile > default.
  • Only difference: is the value sensitive. Secrets get encrypted at rest, redacted in logs, never echoed into CI config.

Treating them as one feature with a Sensitive=true flag (not two parallel mechanisms) keeps the mental model small.

What "first-class" buys consumers

Today in C#:

[Parameter] readonly string Environment = "dev";
[Parameter] readonly string ApiUrl => $"https://{Environment}.example.com/api";

Works, but every consumer reinvents interpolation, and [GitHubActions]-generated workflows can't express "use this variable in the step args" — it's hardcoded at gen-time.

First-class variables let you write:

// .fallout/parameters.json
{
  "Variables": {
    "Environment": "dev",
    "ApiUrl": "https://${Environment}.example.com/api"
  },
  "Secrets": {
    "ApiKey": "v2:..."   // existing encrypted-secret shape
  }
}

and reference ${ApiUrl} from a tool argument, a CI step's env: block, an artifact path. The resolver handles substitution, layering, redaction.

Open design questions

  • Substitution syntax. ${name}, {{ name }}, <<name>>? Pick one and document it.
  • Where substitution happens. Parameter-injection time (so [Parameter] readonly string ApiUrl sees the resolved string)? Tool-invocation time? Both, with attribute opt-in?
  • Profile/environment layering. Partly exists via parameters.<name>.json profiles. Variables + secrets should follow it. Document the precedence chain.
  • CI-config generators. Do [GitHubActions] / [AzurePipelines] emit ${{ env.X }} placeholders the CI engine resolves, or resolve at gen-time? Probably placeholders for secrets (no leaking into committed YAML), gen-time for non-sensitive variables.
  • External secret managers (Track external secret-manager integrations (1Password, Bitwarden, Vault, …) #168 — 1Password, Bitwarden, Vault). The variable layer is the natural plug-in surface: secrets.VaultRef("my-org/api-key") resolved at injection time. Sketch the seam now even if backends land later.
  • Plugin SDK touch points. v12 ships Fallout.Plugin.Sdk; variable resolvers and secret backends are natural extension points. Decide whether the variable subsystem ships before the SDK (internal seams) or as part of it (public seams).

Milestone

Maintainer pick:

Chris's call. Both defensible; v12 feels right for the public-API shape, but an internal v11 cut works if internal value is high enough.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttarget/backlogNo committed release year; long-tail / demand-driven

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions