From 14cc56b25f6e647b1cea35477eed27f0372f1ba8 Mon Sep 17 00:00:00 2001 From: Scott Werner Date: Mon, 17 Aug 2026 17:01:04 -0400 Subject: [PATCH] Remove stale env-interpolation promises from docs Config {{ env.NAME }} interpolation was removed workspace-wide (tokens still parse only to fail with a migration message), but several doc comments and the server-secrets strategy doc still presented it as a live mechanism, including run goal file paths where the new workflow-version validation now makes the contradiction user-visible. Co-Authored-By: Claude Fable 5 --- docs/internal/server-secrets-strategy.md | 16 +++++++--------- .../fabro-workflow/src/operations/source.rs | 4 ++-- lib/components/fabro-workflow/src/run_options.rs | 4 ++-- lib/foundation/fabro-config/src/layers/run.rs | 6 ++---- lib/foundation/fabro-model/src/catalog.rs | 8 ++++---- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/docs/internal/server-secrets-strategy.md b/docs/internal/server-secrets-strategy.md index 76f12087d..839f2bcec 100644 --- a/docs/internal/server-secrets-strategy.md +++ b/docs/internal/server-secrets-strategy.md @@ -13,7 +13,7 @@ when does it resolve** — see [Which process resolves what](#which-process-reso - Resolution is snapshot-based: env and file are read once at construction, then treated as immutable for the life of the process. - `process env` wins over `server.env` on conflicts. - Optional integration secrets are vault-only in the **server process**. Do not add optional server integrations to `ServerSecrets`, and do not add bespoke env fallback paths to it. -- Not every credential is a `ServerSecrets` or vault lookup. A third mechanism exists: **settings-declared credentials** in `InterpString` fields, resolved at consumption time from `{{ env.NAME }}` or `{{ secrets.NAME }}`. See [Settings-declared credentials](#settings-declared-credentials). +- Not every credential is a `ServerSecrets` or vault lookup. A third mechanism exists: **settings-declared credentials** in `InterpString` fields, resolved at consumption time from `{{ secrets.NAME }}`. See [Settings-declared credentials](#settings-declared-credentials). - `fabro server start` never generates secrets. Missing required secrets are a startup error. - `std::env::set_var` and `std::env::remove_var` are banned workspace-wide. Tests are not exempt. Enforced by clippy via `disallowed_methods` in `clippy.toml`; intentional exceptions must be annotated with a scoped `#[expect(clippy::disallowed_methods, reason = "...")]` at the call site. @@ -70,7 +70,6 @@ than saying "server runtime", which is ambiguous. | Bootstrap server secret | Server process, via `ServerSecrets` | Once at construction, then immutable | | Optional integration secret | Server process or worker, via the vault | At use | | `{{ vars.NAME }}` | Server process | When the run is created, from that run's variable snapshot | -| `{{ env.NAME }}` | The process that owns the value (usually the worker) | At consumption time | | `{{ secrets.NAME }}` | The process that owns the value, against the server vault | At consumption time | `docs/public/agents/mcp.mdx` documents the same split for MCP server configuration and is a good @@ -80,18 +79,17 @@ worked example of the shape. Some credentials are declared in settings rather than looked up by name. Those fields are `InterpString` (`lib/foundation/fabro-types/src/settings/interp.rs`), which supports narrow -`{{ namespace.NAME }}` tokens with no template logic. Three namespaces resolve: `env` (process -environment, consumption time), `secrets` (vault, consumption time), and `vars` (non-sensitive run -variables, substituted early at run creation). A token whose namespace is unavailable in the -resolution context fails loudly. +`{{ namespace.NAME }}` tokens with no template logic. Two namespaces resolve: `secrets` (vault, +consumption time) and `vars` (non-sensitive run variables, substituted early at run creation). +`{{ env.NAME }}` tokens still parse but never resolve; they fail loudly with a migration message. A +token whose namespace is unavailable in the resolution context also fails loudly. -The reference implementation is LLM provider `extra_headers`, resolved against env plus vault at +The reference implementation is LLM provider `extra_headers`, resolved against the vault at `lib/foundation/fabro-auth/src/resolve.rs:376-378`: ```toml [llm.providers.example.extra_headers] authorization = "Bearer {{ secrets.EXAMPLE_TOKEN }}" -x-tenant = "{{ env.EXAMPLE_TENANT }}" ``` Use this mechanism when the credential belongs to an operator-configured integration declared in @@ -149,7 +147,7 @@ First pick the mechanism. These are the only three: |---|---|---| | Bootstrap server secret | Platform env or install-written `server.env` | `state.server_secret(...)` | | Optional integration secret | Vault (`fabro secret set`, `fabro install`) | `state.vault_secret(...)` | -| Settings-declared credential | `{{ secrets.* }}` or `{{ env.* }}` in an `InterpString` settings field | Resolved at consumption time by the owning process | +| Settings-declared credential | `{{ secrets.* }}` in an `InterpString` settings field | Resolved at consumption time by the owning process | Then: diff --git a/lib/components/fabro-workflow/src/operations/source.rs b/lib/components/fabro-workflow/src/operations/source.rs index 3e275937c..195566b7a 100644 --- a/lib/components/fabro-workflow/src/operations/source.rs +++ b/lib/components/fabro-workflow/src/operations/source.rs @@ -109,8 +109,8 @@ pub(crate) fn resolve_workflow(request: ResolveWorkflowInput) -> anyhow::Result< /// Resolve the `run.goal` override for a direct (non-manifest) workflow /// run. Reads the file from disk if the goal layer is the `file` variant. -/// Relative paths that survived config load (e.g. env-interpolated ones) -/// are anchored at `working_directory`. +/// Relative paths that survived config load are anchored at +/// `working_directory`. fn resolve_goal_override( settings: &WorkflowSettings, working_directory: &Path, diff --git a/lib/components/fabro-workflow/src/run_options.rs b/lib/components/fabro-workflow/src/run_options.rs index 46fffe790..7c4ca98d6 100644 --- a/lib/components/fabro-workflow/src/run_options.rs +++ b/lib/components/fabro-workflow/src/run_options.rs @@ -78,8 +78,8 @@ pub struct LifecycleOptions { } /// A single setup (prepare) command and the per-step environment it runs with. -/// Both the command string and the env values are already fully resolved (their -/// `{{ env.* }}` tokens replaced at the run boundary) by the time they reach +/// Both the command string and the env values are already fully resolved +/// (interpolation tokens replaced at the run boundary) by the time they reach /// the sandbox. pub struct SetupCommand { pub command: String, diff --git a/lib/foundation/fabro-config/src/layers/run.rs b/lib/foundation/fabro-config/src/layers/run.rs index f81b4e0f6..8b020666f 100644 --- a/lib/foundation/fabro-config/src/layers/run.rs +++ b/lib/foundation/fabro-config/src/layers/run.rs @@ -116,10 +116,8 @@ impl Combine for RunIntegrationsGithubLayer { /// /// Relative paths inside the `file` variant are resolved against the /// directory of the config file that declared them at load time (see -/// `fabro_config::resolve_goal_file_paths`). `{{ env.NAME }}` interpolation is -/// supported inside the `file` path; env-tokenized relative paths stay -/// unresolved until consume time and are then resolved against the run's -/// effective working directory. +/// `fabro_config::resolve_goal_file_paths`). Interpolation tokens are not +/// supported inside the `file` path; a tokenized path fails to resolve. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum RunGoalLayer { diff --git a/lib/foundation/fabro-model/src/catalog.rs b/lib/foundation/fabro-model/src/catalog.rs index 7ff4b266f..56c0f5b0c 100644 --- a/lib/foundation/fabro-model/src/catalog.rs +++ b/lib/foundation/fabro-model/src/catalog.rs @@ -61,8 +61,8 @@ pub struct ProviderCatalogSettings { pub api_key_url: Option, #[serde(default)] pub base_url: Option, - /// Unresolved interpolation source strings (literal text, `{{ env.NAME }}`, - /// or `{{ secrets.NAME }}` tokens), resolved at the credential boundary in + /// Unresolved interpolation source strings (literal text or + /// `{{ secrets.NAME }}` tokens), resolved at the credential boundary in /// `fabro-auth`. #[serde(default)] pub extra_headers: Option>, @@ -438,8 +438,8 @@ pub struct CatalogProvider { pub billing_policy: BillingPolicy, pub api_key_url: Option, pub base_url: Option, - /// Unresolved interpolation source strings (literal text, `{{ env.NAME }}`, - /// or `{{ secrets.NAME }}` tokens), resolved at the credential boundary in + /// Unresolved interpolation source strings (literal text or + /// `{{ secrets.NAME }}` tokens), resolved at the credential boundary in /// `fabro-auth`. pub extra_headers: HashMap, pub priority: i32,