feat(server): forward OTLP export config to the worker subprocess (O1 Lever B)#1
Conversation
The server spawns `__run-worker` via apply_worker_env, which env_clear()s and copies a narrow allowlist that omits OTEL_*. The worker installs the otel_layer (fabro-cli) but never sees the exporter env, so its spans are never exported. Re-inject the server's OTLP export config into the worker after apply_worker_env: forward the NON-SECRET export vars (endpoint / protocol / service-name, base + per-signal), and explicitly strip the credential- bearing OTEL_EXPORTER_OTLP_HEADERS / _TRACES_HEADERS so the collector's egress key can never reach the sandboxed worker. The worker exports to the LOCAL collector, which adds egress auth itself. Additive and opt-in: with no OTLP env on the server nothing is forwarded and there is no behavior change. A unit test asserts the five non-secret vars forward and both headers spellings are stripped even when pre-set.
Address the Codex + Fable adversarial review of the worker OTLP re-injection (all findings low/minor/nit; the security invariant was confirmed to hold): - Forward OTEL_EXPORTER_OTLP_TIMEOUT / _TRACES_TIMEOUT too (Codex): the opentelemetry-otlp exporter reads them from env, so the worker's exporter now mirrors the server's full non-secret export config, not just endpoint/protocol/service. Listed by literal name (no fabro_static constant). - Pre-set BOTH headers vars in the unit test before apply_otel_export (Codex + Fable): previously only OTEL_EXPORTER_OTLP_HEADERS was pre-set, so the _TRACES_HEADERS denylist entry was not regression-guarded. Both strips are now load-bearing. Timeout forwarding is asserted too. - Correct the allowlist comment (Fable): the fail-closed guarantee is env_clear + allowlist-only forwarding; the headers denylist is belt-and-suspenders for an accidental explicit set after the call, not the primary control. Note the local-no-auth-collector topology assumption. - Make the ordering requirement explicit at the worker_runtime.rs call site (Fable): the re-injection MUST stay after apply_worker_env's env_clear. fmt/clippy(-D warnings)/spawn_env tests green under nightly-2026-04-14.
Adversarial review complete (Codex + Fable), findings addressedReviewed with the transport's two-reviewer pattern. Fable (executing) ran clippy + tests and confirmed the security invariant holds: env_clear + allowlist-only forwarding is fail-closed; no path lets
Not added: a process-env integration test over Validated under the pinned Ready to integrate into |
…ook landed Update the handoff NEXT ACTION: Lever B is no longer a TODO — it is built, adversarially reviewed (Codex + Fable), and ready as fork PR thewoolleyman/fabro#1, green under the pinned toolchain. Lever A's runbook step landed (#702). What remains is a single operator window: merge #1, rebuild + re-pin the host binary, restart with the Lever A OTEL env, then proof-dispatch. O2 follows O1's proof. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…IGPIPE O1 is proven end-to-end (2026-07-17): the full operator cutover ran — merged fork PR thewoolleyman/fabro#1, re-pinned the host binary (c543446, Lever B), restarted the server with the Lever A OTEL env, and proof-dispatched bd-ib-dqt (green, PR #706 merged). Honeycomb's fabro dataset shows the run span at count=2/root_count=2 — the server + worker run-span pair (two distinct traces), service.name=fabro, via the receiver. Handoff NEXT ACTION updated accordingly; O2 (traceparent) is next. Also fix a pre-existing SIGPIPE in build-and-verify.sh: the version probe used the multi-line 'fabro version' subcommand piped to 'head -1', which closed the pipe early and panicked the Rust binary (fatal under set -o pipefail). Use the single-line 'fabro --version' instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Activates the inert worker OTLP exporter for the dark factory (livespec O1
bd-ib-98c.4, Lever B). Companion to the transport PR fabro-sh#576 (already carried infactory-integration).Problem
The server spawns
__run-workerviaapply_worker_env, whichenv_clear()s and copies a narrow allowlist (WORKER_ENV_ALLOWLIST) that omits allOTEL_*. The worker installsotel::otel_layer()(fabro-cli) but, with no OTEL env, never exports — so the agent-side spans are lost.Change
Re-inject the server's OTLP export config into the worker after
apply_worker_env, via a newapply_worker_otel_export_env:env_removes the credential-bearingOTEL_EXPORTER_OTLP_HEADERS/_TRACES_HEADERS.Security invariant
The headers vars carry the collector's egress credential (a Honeycomb API key), and the
opentelemetry-otlpexporter readsOTEL_EXPORTER_OTLP_HEADERSfrom the env itself. They must never reach the sandboxed worker — the worker exports to the local collector, which adds egress auth. A unit test proves the five non-secret vars forward and both headers spellings are stripped even when pre-set.Additive / opt-in
With no OTLP env on the server, nothing is forwarded and there is no behavior change.
Validation (pinned
nightly-2026-04-14)cargo fmt --check --all— cleancargo clippy --locked -p fabro-server --all-targets -- -D warnings— cleancargo test -p fabro-server --lib spawn_env— 3 passedDraft pending Codex + Fable adversarial review and the maintainer's integrate + re-pin decision (Lever B pairs with Lever A, the server-start OTEL env; see the livespec O1 plan). End-to-end proof-dispatch is deferred until the pinned host binary is rebuilt from this + the server restarted with OTEL env.