The API server set RunConfig.dry_run but never called
engine.set_dry_run(), so command/script nodes executed for real
during API-served dry runs. Add the missing call.
Also extract EngineServices::test_default() to replace 11 identical
make_services() bodies and 8 inline struct constructions across
handler test modules (-254/+57 lines).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When running `fabro run smoke`, the slug "smoke" was used to locate the
workflow but never persisted. If the DOT graph name diverged from the
directory name (e.g. workflows/foo/ contains digraph Bar), resolve_run
couldn't find the run by slug. Now the slug is extracted from the
workflow path, stored in the manifest, and matched in resolve_run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add project-level run defaults to fabro.toml so sandbox, LLM, hooks,
MCP servers, and other settings can be shared across workflows instead
of duplicated in each workflow.toml. Precedence: workflow.toml >
fabro.toml > cli.toml/server.toml.
- Rename `directory` → `work_dir` with backwards-compat serde alias
- Add `hooks` and `mcp_servers` to `RunDefaults` with merge logic
- Extend `ProjectConfig` with all run-defaults fields + `into_run_defaults()`
- Remove duplicate `McpServerEntry` from fabro-config (use run_config's)
- Move `hook_config` from ServerConfig into `run_defaults.hooks`
- Wire project config merge and hooks/mcp fallbacks in run_command()
- Update OpenAPI spec and regenerate TypeScript client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename 79 workflow files from .dot to .fabro extension across
fabro/workflows/, test/, test/docs/, and files-internal/demo/.
Update TOML configs, Rust production code, test code, and shell
scripts. Backward compat tests in test/attractor/ are unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>