## Summary
Moves provider-specific facts out of `AdapterKind` metadata and into
provider catalog data, leaving adapters responsible for runtime protocol
behavior. This makes providers that share an adapter mostly TOML-driven
while still surfacing adapter construction failures during readiness
checks.
## What Changed
- Provider TOML now owns auth mode, API-key/header policy, billing
policy, agent profile, base URLs/env overrides, extra headers, and probe
markers.
- Auth, install, config, diagnostics, and server flows resolve provider
credentials from catalog auth config, including API-key, header-only,
and no-auth providers.
- LLM client registration now reports adapter construction failures,
validates final adapter requests before HTTP dispatch, and preserves
custom primary auth headers.
- Billing and docs now use provider-owned billing policy instead of
adapter metadata, and the old adapter metadata surface is removed.
## Reviewer Notes
OpenAI-compatible `base_url` validation now happens during
adapter/client registration rather than catalog build. That keeps
catalog parsing adapter-agnostic while still letting readiness and model
listing reflect providers that cannot register.
## Verification
- `cargo check -p fabro-model -p fabro-auth -p fabro-llm -p fabro-server
-p fabro-cli`
- `cargo nextest run -p fabro-llm -- adapter_registry`
- `cargo nextest run -p fabro-model -- catalog`
- `cargo nextest run -p fabro-auth -- api_key`
- `cargo nextest run -p fabro-server -- install`
- `cargo +nightly-2026-04-14 fmt --check --all`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Template failures from `fabro run` and structural warnings from `fabro
validate` now preserve source provenance through rendering, workflow
transforms, API serialization, and CLI display. Diagnostics can point at
the actual workflow, import, or prompt file with node/attribute context
instead of surfacing MiniJinja's generic `<string>` source.
## What Changed
- Added named MiniJinja render APIs plus miette-aware `TemplateError`
metadata for source names, source text, spans, and labels.
- Reworked workflow template expansion so inline attributes, imported
workflows, and `@prompt` files render with file and owner context.
- Split strict run behavior from structural validate behavior: run-start
still hard-fails on missing inputs, while validate emits source-aware
warnings and continues linting.
- Extended validation diagnostics through Rust structs, OpenAPI, server
DTO mapping, and CLI rendering with optional source path, line, column,
span, and related metadata.
- Added regression coverage across template rendering, workflow
transforms, CLI output, and the server validate endpoint.
## Verification
- `cargo nextest run -p fabro-template`
- `ulimit -n 4096 && cargo nextest run -p fabro-workflow --no-fail-fast`
- `cargo nextest run -p fabro-cli
bare_fabro_with_unbound_inputs_validates_structurally_with_warning
run_rejects_unbound_template_inputs_before_creating_remote_run`
- `cargo nextest run -p fabro-server
validate_endpoint_returns_template_source_coordinates`
- `cargo build -p fabro-api`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Aleksi Asikainen <1086393+salieri@users.noreply.github.com>
## Summary
Add CLI support for run parent relationships now that the server API can
store them. This lets users create child runs, filter children, inspect
parent metadata, and link or unlink parents without dropping to raw API
calls.
## What Changed
- Added top-level `fabro parent link` and `fabro parent unlink` commands
with selector resolution, text output, and JSON summaries.
- Added `--parent` to `fabro run`, `fabro create`, and `fabro ps`;
create/run send `parent_id` in manifests and `ps` uses server-side
parent filtering.
- Surfaced `parent_id` in `ps --json` and `inspect`, with a conditional
`PARENT` column for unfiltered tables.
- Extended `fabro-client` parent-link APIs and
`list_store_runs(parent_id)`.
## Test Plan
- `cargo nextest run -p fabro-cli`
- `cargo nextest run -p fabro-client`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-cli -p fabro-client
--all-targets -- -D warnings`
- `cargo insta pending-snapshots`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
This PR moves Fabro’s provider/model catalog toward settings-driven
provider identity by replacing the closed provider schema at the
API/auth/model boundary with `ProviderId`, then loading built-in
provider and model metadata from embedded per-provider TOML files.
The immediate result is that built-ins now use the same settings-shaped
catalog data that custom providers will use later, while request-serving
paths still keep the existing bootstrap/default catalog behavior until
the resolved-catalog plumbing lands.
## Changes
- Replaces API-facing provider enum usage with string-backed
`ProviderId`, including OpenAPI/progenitor replacements and regenerated
TypeScript client models.
- Routes model, auth, billing, CLI, server, and workflow call sites
through provider IDs where they cross product identity boundaries.
- Builds `Catalog` from settings-shaped provider/model data with
validation for adapter keys, OpenAI-compatible `base_url`, duplicate
aliases, provider defaults, disabled entries, model controls, and
per-speed cost rows.
- Replaces `catalog.json` with embedded provider TOML files under
`lib/crates/fabro-model/src/catalog/providers/`.
- Adds an explicit `fabro_model::bootstrap_catalog` hatch for
setup/install paths and extends the dev policy test to keep bootstrap
access contained.
- Preserves public training and knowledge-cutoff labels in LLM model
settings while still accepting bare TOML dates.
## Verification
- `cargo nextest run -p fabro-model -p fabro-config -p fabro-api` — 416
passed
- `cargo nextest run -p fabro-dev --features dev
bootstrap_catalog_references_stay_in_allowlist` — 1 passed
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo build --workspace`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
- `fabro validate path/to/workflow.fabro` now auto-discovers a sibling
`workflow.toml` and loads its `[run.inputs]`, so templated graphs
validate the same way they do when invoked by name or by toml path.
- The discovery is opt-in to the user's specific graph: we only pick up
the sibling toml if its `[workflow].graph` resolves back to the `.fabro`
the user passed. Unrelated tomls in the same directory are ignored.
## Why
`fabro validate` is the natural fast-feedback tool for CI/pre-commit
hooks that iterate on changed `.fabro` files. Previously, a graph using
`{{ inputs.* }}` would fail with a generic MiniJinja "undefined value"
error when validated by path, even when a sibling `workflow.toml`
defined those inputs. The other two invocation forms (by name, by toml)
worked, which made the path form a usability cliff.
Fixes#195.
## Test plan
- [x] New integration test:
`bare_fabro_picks_up_sibling_workflow_toml_inputs` validates
`test/templated_inputs/workflow.fabro` (uses `{{ inputs.app_dir }}`) and
expects `Validation: OK`.
- [x] New unit tests in `fabro-config::project`:
- `resolve_workflow_path_picks_up_sibling_workflow_toml` — happy path.
- `resolve_workflow_path_ignores_sibling_toml_pointing_elsewhere` —
guard: don't apply an unrelated sibling toml.
- [x] `cargo nextest run --workspace` — 5585 tests pass.
- [x] `cargo +nightly-2026-04-14 fmt --check --all`, `clippy --workspace
--all-targets -- -D warnings` clean.
- [x] Manual: `fabro validate /tmp/fabro-issue-195/workflow.fabro`
(templated graph + sibling toml with `[run.inputs]`) prints `Validation:
OK`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Nate Aune <118984+natea@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make run.created the projection anchor and require canonical run spec/status fields in API and clients.
Collapse diff/checkpoint/conclusion payloads around RunDiff and update server, CLI, workflow, store, and generated clients.
Persist resolved run titles on creation, expose title update events, and add the run title PATCH API. Regenerate API clients and refresh web/server invalidation so title changes are reflected across run detail and board views.
Make local sandbox execution direct by removing the public worktree mode and in-place controls from CLI, config, run state, API surfaces, docs, and UI. Keep worktree support only for internal parallel-node isolation.
## Summary
Removes Fabro's automatic retro generation stage so workflow runs go
directly from execution to finalization and optional PR creation. This
drops the retro-specific crate, events, projection fields, config/API
knobs, and user-facing docs in favor of the existing durable run
observability surfaces.
## What Changed
- Deleted the `fabro-retro` crate and the workflow `retro` pipeline
phase, with finalization now consuming `Executed` state directly.
- Removed retro configuration and API surface area, including
`--no-retro`, `[run.execution].retros`, manifest `no_retro`,
`features.retros`, and run projection `retro*` fields.
- Retired typed `retro.*` events while keeping historical event logs
readable by deserializing retired retro event names as `Unknown`.
- Stopped appending retro sections to generated PR bodies and updated
docs, marketing copy, screenshots, and navigation to point users toward
observability/event-stream inspection.
## Testing
Not run during PR creation; this branch already contained the
implementation commit.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, reasoning unspecified) via
[Codex](https://openai.com/codex)
Emit local sandbox stop events so CLI follow and event-history tests can observe terminal cleanup under run-owned sandbox lifecycle. Fall back to stored file diffs when completed runs no longer have an active sandbox.
Route async API failures through the body-preserving classifier and add run-create context so CLI output keeps server response details in the cause chain.
### Summary
Silent fallback paths now emit stable warnings instead of degrading
without a user-visible signal. The fallback behavior is unchanged; runs
still continue, but worktree, Git, checkpoint, and LLM failover issues
now show up in the run feed and logs.
### Plan Summary
- Emit run notices for workflow Git/worktree fallback paths.
- Reuse the existing failover event for one-shot LLM provider fallback.
- Add tracing for sandbox pipe drain failures.
### What changed
- Added `worktree_skipped_no_git` and gated `sandbox_git_unavailable`
notices during initialization.
- Added `git_push_failed` and `parallel_base_checkpoint_failed` notices,
including redacted output tails where available.
- Logged GitHub token mint failures with a structured `error` field
before the existing notice.
- Plumbed `Emitter` and `StageScope` through `CodergenBackend::one_shot`
so the API backend emits the existing `agent.failover` event instead of
a duplicate tracing-only warning.
- Extracted sandbox pipe draining into a helper that warns on
stdout/stderr read failures, with unit coverage for the error path.
- Updated CLI snapshots for the new worktree warning in stderr and JSON
event output.
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
So that CLI and other API consumers can surface a clickable link to the
run's web UI page instead of guessing route shapes or probing settings.
The server populates `web_url` from `server.web.enabled` and
`server.web.url`, returns it on `RunStatusResponse` (create plus all
lifecycle transitions), and persists it on the `run.created` event so
attach replays the same link without re-deriving it.
CLI: prints `Web UI: <url>` as a run-header info line, driven off the
replayed event so fresh runs and `attach` share one code path. Absent
when the UI is disabled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace 16 inline copies of the `[cli.target] type = "http"` settings TOML across CLI integration tests with a single `set_http_target(&base_url)` method on `TestContext`. Removes a brittle format string that was maintained in ten files but only meaningfully asserted-against in one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop --allow-no-checkpoints and the paired ManifestArgs in_place /
allow_no_checkpoints fields. The CLI now translates --in-place into a
single ManifestArgs.worktree_mode = "never" signal that flows through
the existing args→layer pipeline as run.sandbox.local.worktree_mode =
Never. The server computes prepared.in_place from the resolved settings
once, replacing the trio of bail!s and the sandbox-default fixup.
Skip worktree checkpoint setup when a local sandbox is not backed by a git repository, and keep the API contract aligned with RunSpec serialization for omitted labels.
Move server-only settings reads out of user-facing CLI commands into a
dedicated local_server module, the install/uninstall exceptions, and the
worker subcommand. Adds bin/dev/check-boundary.sh to prevent regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the plan at
docs/plans/2026-04-20-003-refactor-unify-run-vocabulary-metadata-plan.md.
- Rename RunRecord to RunSpec and RunProjection.run to .spec everywhere
in Rust source, tests, helpers, test names, and error messages.
- Introduce SerializableProjection wrapper that trims bulky node text
fields (prompt, response, diff, stdout, stderr) for run.json snapshots.
- Collapse metadata-branch and CLI export to one RunDump::from_projection
builder emitting run.json + graph.fabro + stages/{stage_id}/... and
drop legacy top-level start/status/checkpoint/sandbox/retro/conclusion
split files.
- Replace MetadataStore::write_checkpoint with write_snapshot returning
the commit SHA; add read_run_projection/read_run_spec; demote
read_checkpoint/read_start_record to projection-field extractors.
- Switch fork, rewind, rebuild_meta, CLI rewind recovery, and retro
upload to read the unified projection layout.
- Add additive query methods on RunSpec and RunProjection.
Serde-level `alias = "spec"` shim dropped; `rename = "run"` retained to
keep the server API wire format stable per the plan's scope boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2/3 of the std::fs lint initiative (Phase 1 refactors landed in
commit 9d1c0d98c).
clippy.toml additions (appended to disallowed-methods):
std::fs::read, read_to_string, write, read_dir, copy, canonicalize
std::fs::File::open, File::create, File::create_new
std::fs::OpenOptions::open
File::options was deliberately excluded — it returns an OpenOptions
builder with no syscall. OpenOptions::open is where the block happens.
Non-blocking std::fs items (metadata, exists, create_dir_all, remove_*,
rename, and all std::fs types) remain legal.
Annotation policy (per updated plan):
- Mixed async/sync production source: function- or statement-scoped
#[expect(...)] so future accidental Tokio-path regressions in the
same file still fire.
- Fully-sync production source, test modules, integration tests,
build.rs: file-level #![expect(...)].
- Every #[expect] has a specific reason identifying the sync context.
Annotations added in ~90 files across the workspace. Notable narrow
placements: fabro-server server.rs current_server_target,
build_disk_usage_response, create_test_app_state_with_session_key;
fabro-server install.rs read_to_string rollback snapshot;
fabro-sandbox local.rs list_recursive; fabro-agent cli.rs FOLLOW-UP on
the JSON-stdout writer; fabro-llm providers/common.rs FOLLOW-UP for
load_file_as_base64 (7 translator call sites; revisit if file:// URL
usage grows).
build.rs blanket allows: fabro-api/build.rs, fabro-util/build.rs.
Pre-existing unrelated nightly-clippy warnings fixed under scope:
fabro-sandbox sandbox_spec.rs (unused_imports, unused_async),
reconnect.rs (unused_variables, unused_async).
Verified: cargo +nightly-2026-04-14 clippy --workspace --all-targets
-- -D warnings passes; fmt clean; 4129/4131 tests pass (two known
flakes under parallel nextest load, both pass individually and are
unrelated to this change).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add [GRAPH_PATH] filter to run_output_filters so dry_run_simple
snapshot is path-independent
- Double fabro-cli slow-timeout (3s → 6s) to prevent ps test timeouts
- Preserve cloud sandboxes; bump snapshot to fabro-v7 with 8 CPU / 16GB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config discovery walks from the workflow file's parent directory, so
tests using fixtures at their repo path (test/simple.fabro) would find
the repo's .fabro/project.toml. This caused settings like preserve=true
to leak into tests and break sandbox cleanup event assertions.
Add TestContext::install_fixture() which copies fixtures into the test's
temp dir. Update all CLI run/attach/start tests to use it. Remove the
now-unused example_fixture() function.
Restore preserve=true in .fabro/project.toml — tests are now isolated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove EnvGuard and env-mutating test from fabro-auth (shared mutable state)
- Revert project.toml preserve=true that broke sandbox cleanup event tests
- Fix clippy: use is_some_and, scoped imports for StageStatus and render
- Update snapshot tests for new Run: ULID line and model_test output
- Fix preflight test assertion (name said "allows", asserted failure)
- Update cancel_queued_run test: cancelled runs now appear on board
- Add graph direction query param support to get_graph endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve CLI settings once from user config plus process-local overrides
and pass the resolved view through command dispatch and CommandContext.
This keeps config-driven cli.output, cli.updates, and cli.logging
behavior working while preserving commands that only reject explicit
--json overrides. It also removes the implicit auto-approve coupling
from JSON run output.
Conflicts resolved:
- install.rs: kept simplified auth (port 32276, no TLS, no username
in merge_server_settings), adapted to origin's input_source API by
removing username from ServerConfigSelection::Write
- serve.rs: kept ProviderCredentials import from origin, dropped
ClientAuth (removed with mTLS)
- server.rs: kept both imports (ServerAuthMethod + Provider)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass the shared storage dir into worker runs so vault-backed credentials
load during real workflow execution, including server-spawned workers.
Also finish the QA follow-ups around scripted install behavior, list
credential metadata in secret listings, and give the slow OpenAPI
conformance test a narrow nextest timeout override.
Propagate the local dev token through worker subprocesses, share the
same authenticated local-server helper across CLI integration tests,
and clean up the async token wait path so fmt, clippy, and full tests
pass again after the dev-token auth rollout.
Embed defaults.toml as a base settings layer and apply it when
materializing effective settings and resolving typed settings.
This also fixes partial CLI table merging so builtin fields survive
higher-precedence overrides, and updates the affected CLI tests and
snapshots.
Add a shared MiniJinja-based template crate and migrate workflow prompts,
imports, hooks, and InterpString env references to the new {{ ... }}
syntax. This also threads typed run inputs through workflow rendering and
updates docs and tests to match the new templating model.
Brings in the events schema v2 work (RunEvent envelope fields, ActorRef,
parallel branch ids, flattened EventEnvelope wire JSON) on top of the
local Stage 6 settings TOML redesign.
Conflict resolutions:
- fabro-types/src/lib.rs: keep new ParallelBranchId re-export from
origin; drop the legacy Settings/ArtifactStorage* re-exports (the
flat Settings struct was deleted in Stage 6.3b).
- fabro-server/src/server.rs: keep new ActorRef import from origin;
drop the unused legacy Settings import that came along with it.
- fabro-api-client/src/models/web-settings.ts: keep our deletion. The
remote modification was an incidental TS-client regeneration that
Stage 6.6 already invalidated by collapsing settings DTOs to a
freeform v2 shape.
- fabro-workflow/src/event.rs: rewrite the run_created actor test to
use SettingsFile::default() instead of the deleted Settings type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`--goal-file` was broken in the v2 path: `TryFrom<&RunArgs> for ConfigLayer`
did `let _ = &args.goal_file;`, so clap accepted the flag listed in
`--help` and then silently dropped it. Users running
`fabro run demo --goal-file prompts/goal.md` ended up with no goal at
all (or the DOT graph-level fallback), a regression from the legacy
flat `Settings` shape.
This commit adds first-class support for both inline and file-sourced
goals via a tagged union on `run.goal`. Greenfield decisions:
- **Single field, two variants.** `RunGoalLayer` is an untagged enum
of `Inline(InterpString)` and `File { file: InterpString }`. Makes
`goal XOR goal_file` un-representable in the type system and lets
the v2 merge matrix treat `run.goal` as a single scalar
(last-writer-wins) instead of needing a custom mutual-exclusion
merge rule. Matches the existing `DaytonaDockerfileLayer` pattern.
- **Relative paths are anchored at the file that declared them.**
`ConfigLayer::load(path)` walks the just-parsed `SettingsFile` and
rewrites any literal relative `run.goal.file` path to absolute
using `path.parent()` as the base, via new
`fabro_config::config::resolve_goal_file_paths`. CLI-sourced paths
via `--goal-file` are anchored at CWD in
`overrides::goal_layer_from_args`. Env-interpolated paths
(`${env.GOALS_DIR}/goal.md`) are left unresolved until consume time
and then resolved against the run's working_directory.
- **New accessors, no shims.**
- `run_goal_layer() -> Option<&RunGoalLayer>` — raw variant access.
- `run_goal_inline_str() -> Option<String>` — inline-only, returns
`None` for file-sourced goals.
- `resolve_run_goal(base_dir) -> Result<Option<ResolvedRunGoal>>` —
reads the file from disk if needed, returns text + provenance
(`ResolvedGoalSource::Inline | File { path }`).
- New `ResolveGoalError` enum covers env-lookup and I/O failures.
- Old `run_goal() / run_goal_str()` are **deleted** outright; every
call site has been updated to pick the right variant.
- **CLI wiring (the actual bug fix).** `overrides::goal_layer_from_args`
replaces the two `let _ = &args.goal_file;` lines with real
resolution: `(Some(text), None)` → `Inline`, `(None, Some(path))` →
`File { file: absolute }`. Both-set is rejected by a helper error
and clap already had `conflicts_with = "goal"` as a belt-and-
braces check. Applied to both `RunArgs` and `PreflightArgs`.
- **Manifest builder.** `resolve_manifest_goal` now calls
`args_layer.as_v2().resolve_run_goal()` and
`settings.resolve_run_goal()` in precedence order, then falls
through to the graph-level `@file` sugar if both are absent. The
resolved goal is translated to a `ManifestGoal { text, type_, path }`
by a new `resolved_goal_to_manifest` helper — inline goals get
`type = Value`, file-sourced goals get `type = File` with the
absolute path echoed for provenance.
- **Workflow pipeline.** `fabro-workflow::operations::source::
resolve_goal_override` is rewritten to use `resolve_run_goal`
against the working_directory. The orphaned helper `resolve_goal_file`
(a stub from Stage 4 that was always called with `None`) is
deleted.
- **Server-side manifest.** `fabro-server::run_manifest::
prepare_manifest` stores the CLI-resolved goal as
`RunGoalLayer::Inline`, matching the Stage 4 plan's "CLI owns goal
file reads; server never touches the filesystem for goals"
contract.
## Tests
**Schema** (`fabro-types::settings::accessors`):
- `run_goal_inline_str_returns_source_value` — literal inline variant
- `run_goal_inline_str_is_none_for_file_variant` — file variant
explicitly yields `None` from the inline accessor
- `resolve_run_goal_reads_file_variant_from_disk` — end-to-end file
read with provenance assertion
- `resolve_run_goal_inline_passes_text_through` — inline passthrough
**Config load** (`fabro-config::config`):
- `parse_accepts_inline_goal` + `parse_accepts_file_variant`
- `parse_rejects_goal_with_unknown_sibling_fields` — untagged enum
correctly rejects mixed-shape TOML
- `combine_replaces_file_goal_with_inline_from_higher_layer` and the
reverse — confirms the tagged union merges as a single scalar with
no custom rule needed
- `load_rewrites_relative_goal_file_to_absolute`
- `load_leaves_absolute_goal_file_untouched`
- `load_leaves_env_interpolated_goal_file_untouched`
**CLI overrides** (`fabro-cli::commands::run::overrides`):
- `goal_and_goal_file_together_is_rejected`
- `goal_file_is_anchored_at_cwd_when_relative`
- `absolute_goal_file_is_preserved`
- `inline_goal_builds_inline_variant`
- `empty_args_produce_no_goal_layer`
**CLI integration** (`fabro-cli::tests:🇮🇹:cmd::run`):
- `dry_run_with_goal_file_reads_contents_into_goal` — end-to-end
`fabro run --dry-run --auto-approve --goal-file <path>` and asserts
the file contents appear in the preflight summary. Explicit
regression test for the silently-ignored flag.
- `dry_run_rejects_goal_and_goal_file_together` — clap conflicts_with
## Callsite churn
Every `run_goal() / run_goal_str()` call site updated:
- `fabro-config/src/effective_settings.rs` — 2 test assertions →
`run_goal_inline_str()`
- `fabro-cli/tests/it/cmd/{config,create}.rs` — 3 sites → inline
- `fabro-cli/src/manifest_builder.rs` — rewritten to use
`resolve_run_goal`
- `fabro-workflow/src/operations/create.rs` — 2 sites, test + set
- `fabro-workflow/src/operations/source.rs` — rewritten
- `fabro-server/src/{run_manifest,server}.rs` — set + test assertion
3,782 workspace tests pass (was 3,765, +17 new). `cargo fmt
--check --all` and `cargo clippy --workspace -- -D warnings` are
clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>