SIGN IN SIGN UP

fix(runner): the bridge accepts the tool shape the wire actually sends

/review's security specialist found the seam nobody was testing. The
lease wire carries `tools: []const []const u8` and child_exec_input
emits each name as a JSON `.string` (child_exec_input.zig:87). The
bridge opened its loop with `if (item != .object) continue`, so in
production EVERY declared tool was dropped before isNeverHosted,
resolve, or isHostedToolAllowed ever ran.

The whole suite missed it because `specOf` builds `{name: …}` objects —
the one shape the wire never sends. Tests passed on a shape production
does not produce.

It stayed harmless only because an empty list fell back to the entire
registry: a Fleet received tools by accident rather than by declaration.
Removing that fallback is what turned a silent mismatch into "no Fleet
gets any tool" — github-pr-reviewer would have had no http_request and
could never have posted a review, which is M136 §3 itself.

buildTools now takes a bare string as the name (enabled by
construction — naming it IS the declaration) and keeps the object shape
for callers that carry `enabled`. A shape that is neither is skipped,
same disposition as an unknown name: it asks for nothing, it gets
nothing.

Two tests go through the REAL producer rather than specOf: a declared
http_request resolves, and a declared shell still fails the lease. That
is the assertion that was missing, not another object-shaped one.

tool_bridge.zig crossed 350 with the fix, so the registry splits into
tool_bridge_registry.zig on the seam the spec named before the work
began: what tools exist and how one is built, versus which a hosted
Fleet may have. The registry is the lower layer — the policy file
imports it to prove its allowlist is a subset at comptime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
K
Kishore Kumar committed
f430f626e8ff3b0e801eb937f8ab85fa46b5c603
Parent: 5ee73d4