fabro/lib/crates
Scott Werner 287afd7928
Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528)
## What

Makes hook interpolation typed end-to-end and fail-closed, and removes
the bespoke template engine on HTTP-hook headers.

- **Typed end-to-end.** Hook `command`, `url`, header values, `prompt`,
and `model` are now carried as a typed `InterpString` from the config
resolve layer all the way to the executor. The executor resolves each
segment at hook fire time from the typed value instead of collapsing it
to a `String` and re-parsing it. This mirrors the MCP transport env
resolution boundary (`resolve_transport_env` / `runtime_mcp_server`).
- **Narrow header tokens.** HTTP-hook headers previously ran through
MiniJinja with an env allowlist
(`TemplateContext::with_env_lookup_allowed`). They now resolve through
the same narrow `{{ ns.NAME }}` token resolver as every other hook field
— no template engine, no allowlist.
- **Fail-closed everywhere.** A missing or out-of-scope `{{ env.* }}` /
`{{ secrets.* }}` token in a command, URL, header, prompt, or model is
now a hard error that blocks the hook rather than firing it with a
half-resolved or empty value. Previously command hooks failed closed but
http/prompt/agent hooks failed open (warned and proceeded), which could
dispatch an HTTP request with an empty credential header or run an LLM
call against a half-rendered prompt. Transport-level outcomes (non-2xx
responses, connection errors, unparseable bodies) stay fail-open.

A follow-up cleanup commit removes the template engine's `env` namespace
(`with_env_lookup` / `with_env_lookup_allowed` / the `EnvLookup`
object), which the header path was the last consumer of.

## How

- `fabro-types` and `fabro-hooks` `HookType` / `HookDefinition` now type
the interpolatable fields as `InterpString`. `InterpString` serializes
as its raw source, so persisted run specs and checkpoints round-trip
unchanged.
- The `fabro-config` resolve layer clones the typed `InterpString`
through instead of calling `as_source()`, so the fields no longer leak
unresolved template text — the old "source preservation" `#[expect]`
annotations on the hook resolvers are gone.
- The executor's single `resolve_interp` helper resolves a typed
`InterpString` and is shared by the command, http, prompt, and agent
paths; resolution failure maps to `HookDecision::Block`, which the
runner already reports loudly (error for blocking hooks, warn for
non-blocking).

## Testing

- New unit tests: fire-time resolution from the typed value (no
re-parse), narrow-token header resolution, and fail-closed behavior for
HTTP url, HTTP header, and prompt hooks on a missing variable (the hook
does not fire and the resolution error surfaces).
- Existing hook tests updated and kept green.
- Gates: `cargo build --workspace`, `cargo +nightly-2026-04-14 fmt
--check --all`, `cargo +nightly-2026-04-14 clippy --workspace
--all-targets -- -D warnings`, and `cargo nextest run` for the touched
crates (`fabro-hooks`, `fabro-types`, `fabro-config`, `fabro-template`,
`fabro-workflow`, `fabro-server`, and the `fabro-cli` hook/config
tests), all green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:07:11 -04:00
..
build-support fix(build): refresh embedded git sha on branch commits 2026-05-09 14:10:04 -04:00
fabro-acp fix(workflow): capture configured artifacts once (#337) 2026-05-21 10:52:22 -04:00
fabro-agent fix(mcp): honor inline enabled=false and per-server tool_timeout (#520) 2026-06-24 16:17:27 -04:00
fabro-api feat(server): mcp-servers HTTP API — handlers + AppState wiring (#532) 2026-06-30 15:06:24 -04:00
fabro-auth feat(llm): Amazon Bedrock provider — Converse codec, SigV4 + API-key auth (#459) 2026-06-16 11:46:49 -04:00
fabro-automation refactor(server): extract reusable git-checkout/materialization core (#523) 2026-06-24 16:17:44 -04:00
fabro-checkpoint Demote non-interpolating config fields to plain String (#492) 2026-06-16 13:16:31 -04:00
fabro-cli fix(cli): use server catalog for provider login (#529) 2026-06-26 08:46:38 -04:00
fabro-client fix(cli): use server catalog for provider login (#529) 2026-06-26 08:46:38 -04:00
fabro-config Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00
fabro-core Replace bare unwrap() with documented expect() across production runtim… (#415) 2026-05-26 17:46:39 -04:00
fabro-db Move variables to SQLite storage (#537) 2026-06-30 12:17:46 -04:00
fabro-dev feat(llm): Amazon Bedrock provider — Converse codec, SigV4 + API-key auth (#459) 2026-06-16 11:46:49 -04:00
fabro-dump feat(web): add server-managed Environments CRUD settings UI (#462) 2026-06-13 08:44:38 -04:00
fabro-environment feat(workflow): support overriding cwd for local sandbox provider (#467) 2026-06-14 12:32:32 -04:00
fabro-github fix(github): refresh installation tokens during workflows 2026-05-06 07:15:18 -04:00
fabro-graphviz fix(graphviz): render comments with template braces (#509) 2026-06-15 15:15:01 -04:00
fabro-hooks Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00
fabro-http refactor(static): centralize env var names 2026-04-24 12:29:51 -04:00
fabro-install feat(web): add server-managed Environments CRUD settings UI (#462) 2026-06-13 08:44:38 -04:00
fabro-interview Replace stdin JSONL control pipe with WebSocket worker control bus (#440) 2026-05-27 20:24:25 -04:00
fabro-llm feat(llm): Amazon Bedrock provider — Converse codec, SigV4 + API-key auth (#459) 2026-06-16 11:46:49 -04:00
fabro-macros refactor(dev): simplify generated docs tooling 2026-04-24 18:41:00 -04:00
fabro-manifest Demote non-interpolating config fields to plain String (#492) 2026-06-16 13:16:31 -04:00
fabro-mcp fix(mcp): honor inline enabled=false and per-server tool_timeout (#520) 2026-06-24 16:17:27 -04:00
fabro-mcp-server feat: Add approve/deny run controls to MCP and CLI (#400) 2026-05-25 15:49:57 -04:00
fabro-mcp-store feat(server): mcp-servers HTTP API — handlers + AppState wiring (#532) 2026-06-30 15:06:24 -04:00
fabro-model feat(llm): Amazon Bedrock provider — Converse codec, SigV4 + API-key auth (#459) 2026-06-16 11:46:49 -04:00
fabro-oauth Replace bare unwrap() with documented expect() across production runtim… (#415) 2026-05-26 17:46:39 -04:00
fabro-options-metadata refactor(dev): simplify generated docs tooling 2026-04-24 18:41:00 -04:00
fabro-proc refactor(static): centralize env var names 2026-04-24 12:29:51 -04:00
fabro-redact Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00
fabro-sandbox fabro doctor: check Docker daemon when Docker sandbox is enabled (#525) 2026-06-25 21:09:31 -04:00
fabro-server feat(server): mcp-servers HTTP API — handlers + AppState wiring (#532) 2026-06-30 15:06:24 -04:00
fabro-slack Replace vague expect/panic messages with invariant-explaining messages (#422) 2026-05-27 10:38:20 -04:00
fabro-spa feat(dev): gitignore embedded spa assets 2026-04-26 21:31:11 -04:00
fabro-static feat(llm): Amazon Bedrock provider — Converse codec, SigV4 + API-key auth (#459) 2026-06-16 11:46:49 -04:00
fabro-store feat(web): add server-managed Environments CRUD settings UI (#462) 2026-06-13 08:44:38 -04:00
fabro-telemetry Replace vague expect/panic messages with invariant-explaining messages (#422) 2026-05-27 10:38:20 -04:00
fabro-template Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00
fabro-test feat(web): add server-managed Environments CRUD settings UI (#462) 2026-06-13 08:44:38 -04:00
fabro-tool feat(web): add server-managed Environments CRUD settings UI (#462) 2026-06-13 08:44:38 -04:00
fabro-tracker fix(github): refresh installation tokens during workflows 2026-05-06 07:15:18 -04:00
fabro-types Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00
fabro-util Replace vague expect/panic messages with invariant-explaining messages (#422) 2026-05-27 10:38:20 -04:00
fabro-validate fix(graph): support dotted Fabro graph attributes (#324) 2026-05-20 09:31:08 -04:00
fabro-variable Move variables to SQLite storage (#537) 2026-06-30 12:17:46 -04:00
fabro-vault feat(server): add variables API (#430) 2026-05-27 11:46:36 -04:00
fabro-workflow Hooks: typed end-to-end interpolation, narrow header tokens, fail-closed resolution (#528) 2026-06-30 17:07:11 -04:00