Commit graph

2698 commits

Author SHA1 Message Date
Bryan Helmkamp
41c47dbe12
fix(workflow): emit terminal run event from FINALIZE, not on_run_end
The `WorkflowRunCompleted` / `WorkflowRunFailed` event was emitted from
`EventLifecycle::on_run_end`, a callback the executor fires at the end of
the EXECUTE phase. But the run isn't done at that point — RETRO and
FINALIZE still need to run, and FINALIZE writes the meta branch's finalize
commit. Observers that treat the event as "done" (CLI attach, daemon SSE
consumers) could observe terminal state and act on it before the worker
flushed its remaining writes.

The recovery scenario test exposed this: it deletes the meta branch
right after `fabro run` returns, then asserts the branch is empty. On
loaded CI runners the worker's finalize commit landed after the delete,
recreating the branch and failing the assertion.

Move the terminal event emission to `pipeline::finalize::finalize`, after
`write_finalize_commit`. The lifecycle's `on_run_end` overrides for event
and git become empty (deleted — the trait already provides a no-op
default). Three pieces of cross-cutting state (`final_patch`,
`captured_artifact_count`, the dead `EventLifecycle` reads of
`last_git_sha`) only existed to ferry data from EXECUTE to the terminal
event; deleted those too. The aggregator collapses to a one-line
delegate to `hook.on_run_end`.

`write_finalize_commit` now takes the conclusion as a parameter and
injects it into the projection copy, since the terminal event hasn't run
through the run store yet when the meta branch is written.

`build_terminal_event` is `pub(crate)` so `test_support` helpers (which
stop at EXECUTE) can mirror the production payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:48:56 -04:00
Bryan Helmkamp
3f807f42a6
Merge pull request #169 from fabro-sh/fix/dependabot-openssl-astro
fix(deps): patch rust-openssl and astro security advisories
2026-04-23 15:35:41 -04:00
Bryan Helmkamp
fce31708de
Merge remote-tracking branch 'origin/main' into fix/dependabot-openssl-astro 2026-04-23 15:29:00 -04:00
Bryan Helmkamp
dda6f44d1e
ci: drop check-env-mutation.sh, rely on clippy disallowed_methods
clippy.toml already bans std::env::{set_var,remove_var} via
disallowed_methods, and every existing call site carries a scoped
#[expect(clippy::disallowed_methods, reason = "...")]. The shell grep
is redundant and forced a second, less granular allowlist.

Also update server-secrets-strategy.md to describe clippy as the
enforcement mechanism.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:24:33 -04:00
Bryan Helmkamp
b5b67226f1
ci: restore install.rs server-symbol allowlist and worker-token scrub exemption
Both Boundary checks have been red on main for multiple commits:

- check-boundary.sh: install.rs reintroduced direct use of
  fabro_config::ServerSettings::from_layer in 93b6577cd but was dropped
  from server_symbol_allowlist in bb0d05be2. Re-add it.
- check-env-mutation.sh: the worker FABRO_WORKER_TOKEN scrub added in
  077469d0c is documented as the approved pattern in
  docs-internal/server-secrets-strategy.md but was missing from the
  allowlist. Add the exact line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:20:58 -04:00
Bryan Helmkamp
077469d0c6
refactor(auth): scrub FABRO_WORKER_TOKEN from worker env at startup
The worker subprocess is spawned with env_clear+allowlist by the server, so
the only sensitive value in its env is FABRO_WORKER_TOKEN itself. Read the
token and remove_var it from the process env in main() before Tokio starts
worker threads, then thread it explicitly through runner::execute(&str).

Every descendant (hooks, local sandbox, devcontainer initializeCommand,
MCP stdio, etc.) now inherits a worker env with no bearer in it, so an
unscrubbed spawn site cannot leak the token. This makes the prior denylist
scrub in fabro-hooks and fabro-sandbox redundant — delete it and the shared
WORKER_SECRET_ENV_DENYLIST constant. The sandbox keeps its _api_key/_secret/
_token/_password/_credential suffix heuristic for user-supplied env_vars
hygiene.

Extend the server-dispatched-worker env-leak integration test to also
assert a Bash stage running in the worker does not observe FABRO_WORKER_TOKEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:10:58 -04:00
Bryan Helmkamp
7456cb3252
fix(deps): bump astro from 5.9.3 to 6.1.6
Patches GHSA-j687-52p2-xcff (CVE-2026-41067): XSS in define:vars via
incomplete </script> tag sanitization. Requires Astro >= 6.1.6.

Also bumps @astrojs/react to ^5.0.4 for Astro 6 compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:08:28 -04:00
Bryan Helmkamp
9ed21bcf38
fix(deps): bump rust-openssl from 0.10.75 to 0.10.78
Patches multiple security advisories in rust-openssl (<0.10.78):
- GHSA: Deriver::derive and PkeyCtxRef::derive buffer overflow
- GHSA: Incorrect bounds assertion in AES key wrap
- GHSA: MdCtxRef::digest_final writes past caller buffer
- GHSA: Unchecked callback length in PSK/cookie trampolines
- GHSA: Out-of-bounds read in PEM password callback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:07:10 -04:00
Bryan Helmkamp
fbb924b9c4
chore: fix nightly clippy lints on worker jwt branch
- fabro-client: collapse identical match arms for DevToken/Worker bearer
- fabro-hooks: rewrite filter_map(bool::then) as filter().map() chain
- fabro-sandbox: import WORKER_SECRET_ENV_DENYLIST rather than absolute path
- fabro-server: box large execute_run_in_process future; take path: &str in
  test-only bearer_request; use let-else in session-secret test; replace unit
  pattern _ with () in worker_token request_parts helper; import StatusCode
- fabro-cli run/mod.rs: box large runner::execute future
- fabro-cli worker_auth.rs: drop unused async on shutdown, allow
  clippy::unwrap_used at file level for subprocess test harness setup

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:08:55 -04:00
Bryan Helmkamp
a75e820064
refactor(auth): simplify worker jwt helpers and reuse existing utilities
- drop _pub wrapper parse helpers; make originals pub(crate)
- drop AppState::issue_worker_token thin wrapper
- delete 9 narrating comments the extractor types already encode
- add ActorRef::system_worker() alongside ::user()/::agent()
- share WORKER_SECRET_ENV_DENYLIST from fabro-util between hooks/sandbox
- reuse jwt_auth::bearer_token and session_secret_key_error helpers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:01:22 -04:00
Bryan Helmkamp
0b36ed985b
test(auth): share cli test jwt helpers 2026-04-23 11:42:50 -04:00
Bryan Helmkamp
891b7f90ae
test(auth): backfill worker jwt regression coverage 2026-04-23 11:35:08 -04:00
Bryan Helmkamp
519f46038d
refactor(auth): use per-run worker JWTs for subprocesses 2026-04-23 11:04:45 -04:00
Bryan Helmkamp
e4b6d626a2
plan 2026-04-23 10:39:39 -04:00
Bryan Helmkamp
e210fe9fed
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-cli/src/command_context.rs
#	lib/crates/fabro-cli/src/main.rs
2026-04-23 08:56:40 -04:00
Bryan Helmkamp
a770eb8e12
Merge remote-tracking branch 'origin/main' 2026-04-23 08:52:44 -04:00
Bryan Helmkamp
fc4e0381ac
simplify: dedupe splice helpers, trim dead map wrapper surface
- Collapse splice_model_fallbacks and splice_events into a single
  generic splice_combine guarded by a new SpliceMarker trait; the
  Combine impl for Vec<T: SpliceMarker> replaces the two per-enum impls.
- Drop inherent iter/iter_mut (redundant with Deref/DerefMut),
  AsRef/AsMut, and IntoIterator for &_/&mut _ on ReplaceMap/StickyMap/
  MergeMap — they had zero external callers. DerefMut and IntoIterator
  for Self stay because labels.extend(...) relies on both.
- Drop unused _api parameter on resolve_web.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:52:41 -04:00
Bryan Helmkamp
da9453cbd1
refactor(config): move settings combine into types 2026-04-23 08:42:19 -04:00
Bryan Helmkamp
4ad4d8fd36
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-cli/src/commands/dump.rs
#	lib/crates/fabro-cli/src/commands/store/mod.rs
#	lib/crates/fabro-cli/src/main.rs
2026-04-23 08:42:16 -04:00
Bryan Helmkamp
3bfd93aa60
simplify: add CommandContext::verbose() accessor
The check `ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose`
repeated 6 times across doctor, preflight, run command/resume/mod. Add a
`verbose()` method and replace every call site. `cargo fix` handles the
now-unused `OutputVerbosity` imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:36:52 -04:00
Bryan Helmkamp
7f96069345
docs 2026-04-23 08:35:43 -04:00
Bryan Helmkamp
37cd8ff45f
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/src/lib.rs
#	lib/crates/fabro-server/src/serve.rs
#	lib/crates/fabro-server/src/server.rs
2026-04-23 08:34:44 -04:00
Bryan Helmkamp
829a02f1f8
fix: reorder test imports to satisfy rustfmt
Auto-applied by `cargo +nightly-2026-04-14 fmt --all`. `cargo fix`
earlier inserted the import in a position that violated the grouped
ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:23:39 -04:00
Bryan Helmkamp
04d43785e6
refactor(server): drop EnvSource trait for direct HashMap passing
After StubEnv was removed, the EnvSource trait had only two impls
(ProcessEnv unit struct and a blanket HashMap impl) and tests already
passed HashMaps. Replace the trait with a free `process_env_snapshot()`
function and take `HashMap<String, String>` by value in
`ServerSecrets::load` and the startup validators.

Also flatten `StartupResolution` to a `(AuthMode, ServerSecrets)` tuple
and drop the `StartupValidationError` wrapper in favor of
`anyhow::Result`, and inline the `*_with_lookup` test-only wrappers in
`spawn_env.rs` so tests call `apply_allowlist` directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:22:35 -04:00
Bryan Helmkamp
4318e07ad7
simplify: drop with_server_mode_from_loaded_settings test seam
The cfg(test) helper only re-implemented the production `with_server_mode`
struct literal so tests could inject settings without disk I/O. Three
tests used it, but each one was self-referential — asserting what the
helper itself does rather than exercising production code. Remove the
helper and those three tests.

`synthetic_context_with_settings` is no longer needed either (its
flexibility only mattered for the removed tests); collapse it into
`synthetic_context`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:21:48 -04:00
Bryan Helmkamp
c52e89438b
simplify: fold ResolvedBaseContext into CommandContext
`ResolvedBaseContext` was a staging struct that held pre-context
settings so main.rs could read `user_settings.cli` before committing
to a full `CommandContext`. Now that install no longer needs the
indirection, the staging step doesn't earn its weight.

Give `CommandContext` a direct `from_disk(cli_layer, process_local_json)`
constructor that does the load + printer derivation + struct build in
one shot. main.rs builds one `base_ctx` before the dispatch match and
every arm borrows it — the `build_base_ctx` closure and ~20 duplicate
`let base_ctx = build_base_ctx()?;` lines disappear.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:17:27 -04:00
Bryan Helmkamp
967b1efe76
simplify: migrate install to CommandContext and delete cli_settings local
Public entry points (`execute`, `run_install`, `run_install_github_command`,
`run_install_inner`, `run_install_github_inner`) now take `&CommandContext`
instead of a 4-tuple of `(cli, cli_layer, process_local_json, printer)`.
Extract cli/printer/json from the context once at the top of each.

The nested doctor invocation inside run_install_inner previously built a
fresh `ResolvedBaseContext::from_disk(...).to_context()` with
`process_local_json = false`. Doctor only reads the resolved output
format (`base_ctx.json_output()`), not the invocation flag, so passing
the parent ctx through is behaviorally equivalent and avoids a second
disk load.

main.rs drops the `cli_settings` local entirely — no remaining command
needs it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:12:21 -04:00
Bryan Helmkamp
d51c6dfb76
simplify: migrate exec/parse/workflow/upgrade/uninstall to CommandContext
These top-level commands still took `&CliNamespace` + `Printer`
separately. Thread `&CommandContext` through the public entry points
and pull what's needed (`user_settings().cli`, `printer()`,
`json_output()`) from the context:

- parse: both args were unused — drop entirely.
- workflow list/create: use `ctx.json_output()` / `ctx.printer()`.
- exec: bind `cli = &ctx.user_settings().cli` at the top; drop unused
  printer param.
- upgrade: extract cli/printer inside run_upgrade; leave the private
  run_upgrade_brew helper with its existing signature (unit tests use
  `CliNamespace::default()` directly).
- uninstall: use `ctx.json_output()` / `ctx.printer()`.

Install remains on the old signature — its nested callback structure
makes a larger refactor than this simplification pass warrants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:06:46 -04:00
Bryan Helmkamp
e7099adf3d
refactor(server): drop StubEnv newtype for HashMap EnvSource impl
StubEnv was a thin newtype only used by tests but compiled into every
build. Implementing EnvSource directly on HashMap<String, String> lets
test sites pass a HashMap and removes the type entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:03:00 -04:00
Bryan Helmkamp
f5758d99d0
refactor(config): collapse settings resolver 2026-04-23 08:00:56 -04:00
Bryan Helmkamp
4a696e0706
simplify: inline rendered_config into execute
The helper was a 2-line indirection with a single caller. Inline its
body so the whole command fits in one function.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:00:43 -04:00
Bryan Helmkamp
b98513875a
simplify: return derived context from load_pr_record
`load_pr_record` built a `with_target`-derived context internally and
threw it away, forcing callers to re-derive or fall back to `base_ctx`.
Return the context alongside the record and let close/merge/view use
it directly for printer/json access and github-credentials lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:00:02 -04:00
Bryan Helmkamp
a8623fb996
simplify: flatten commands/store/ after dump rename
The `fabro store dump` -> `fabro dump` rename left `commands/store/` as a
vestigial directory with a stale one-line `StoreRunExport` alias. Move
`dump.rs` and `rebuild.rs` up to `commands/`, import `RunDump` directly,
rename `dump::dump_command` -> `dump::run`, and clean up stale docs and
a noise test that only asserted clap's default error output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:58:26 -04:00
Bryan Helmkamp
72894b3c46
simplify: drop CommandContext::base in favor of ResolvedBaseContext
`CommandContext::base` had a single caller (install.rs) and duplicated
the disk-load path that `ResolvedBaseContext::from_disk` already
provides. Route install through `ResolvedBaseContext::from_disk(...).to_context()`
and drop the standalone constructor. `base_with_settings` stays as the
private shared helper behind both `to_context` entry points.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:54:53 -04:00
Bryan Helmkamp
2e07cf10a1
simplify: borrow cli_settings from ResolvedBaseContext instead of cloning
The `cli_settings` local was a standalone clone of `user_settings.cli`.
Drop the clone and rebind it as `&resolved_base.user_settings().cli`
inside the async block — all callers already borrowed it anyway.
Pre-async uses inline `resolved_base.user_settings().cli.<field>`
directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:52:06 -04:00
Bryan Helmkamp
56667c17a8
fix(server): cover secret boundary enforcement 2026-04-23 07:51:28 -04:00
Bryan Helmkamp
b08cb7631b
simplify: pass CommandContext to secret subcommands
The secret dispatcher pre-computed json/printer and threaded them into
every subcommand. Pass the context directly so each subcommand pulls
what it needs, dropping the fabro_util:🖨️:Printer import from
three files along the way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:50:41 -04:00
Bryan Helmkamp
80a36d1fca
simplify: pass CommandContext to runs archive/rm helpers
`run_bulk` and `remove_from` took separate `json: bool` + `printer`
parameters. Thread the context through instead and pull json/printer
out of it inside the helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:49:07 -04:00
Bryan Helmkamp
c87e282f8c
simplify: gate with_server_mode_from_loaded_settings behind cfg(test)
The helper only exists to let tests inject pre-loaded settings. Inline
the struct-literal into the sole production caller and mark the helper
`#[cfg(test)]` so the test-seam intent is explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:48:04 -04:00
Bryan Helmkamp
cb9b762119
feat(cli): rename store dump to dump 2026-04-23 07:48:03 -04:00
Bryan Helmkamp
5041669d6d
plans 2026-04-23 07:45:26 -04:00
Bryan Helmkamp
eee89aa8e1
Merge remote-tracking branch 'origin/main' 2026-04-23 07:36:48 -04:00
Bryan Helmkamp
897b818a9e
simplify: introduce CommandContext::json_output() accessor
The check `ctx.user_settings().cli.output.format == OutputFormat::Json`
(and its `!=` variant) was repeated 51 times across 36 files. Add a
`json_output()` method on CommandContext and replace every call site.
`cargo fix` handles the now-unused `OutputFormat` imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:34:11 -04:00
Bryan Helmkamp
4fa5896d78
refactor(cli): narrow base context construction 2026-04-23 07:33:21 -04:00
Bryan Helmkamp
b794ec5343
fix(cli): suppress preview open for JSON output 2026-04-23 07:29:37 -04:00
Bryan Helmkamp
07d4aee67c
simplify: drop redundant CommandContext derivations and guards
- ssh/graph: remove `explicit_json_requested() &&` guard before
  `require_no_json_override()` (the call already no-ops without --json).
- pr close/merge/view: drop the outer `with_target` derivation that was
  used only for `printer()` and the output format — both match base_ctx,
  so the derivation was an unused disk-read + settings re-merge.
- command_context tests: collapse `synthetic_context` to delegate to
  `synthetic_context_with_settings`, removing duplicated struct literals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:28:54 -04:00
Bryan Helmkamp
e6e091fe8e
refactor(server): lock down server secrets 2026-04-23 07:15:27 -04:00
Bryan Helmkamp
2b933597b2
refactor(cli): finish command context cleanup 2026-04-23 07:14:32 -04:00
fabro-releases[bot]
7fd8f5a57b Bump version to 0.212.0-nightly.0 2026-04-23 09:45:03 +00:00
Bryan Helmkamp
fbe0bdfbc1
refactor: align cli command boundaries on CommandContext 2026-04-23 01:08:14 -04:00