2806abac44
Slice 3c of issue #17 — the web surface of the bind trigger, server side. Closes the INV-008 lockstep (CLI + TUI + web all carry the bind now). Implements the contract's "web bind split": the browser selects only the PLANE; the consumer key and the Worldtree-visible host are SERVER-HELD config and never reach the browser. - create_app gains bifrost_consumer_key + bifrost_visible_host (server-held, from env via the entrypoint: RATATOSKR_BIFROST_CONSUMER_KEY / RATATOSKR_PROVIDER_VISIBLE_HOST). - _create_session_endpoint reads an optional `bifrost_plane` from the browser body, builds the BifrostBinding SERVER-SIDE via endpoint_for_plane(plane, visible_host), and calls create_session(bifrost=, consumer_key=). The 201 response echoes bound-state {plane, endpoint, status: bound} for the UI indicator — never the key (INV-008/INV-009). - Error routing: invalid plane / unconfigured server -> 400; BifrostHandshakeFailed -> 502 {bifrost_error}; BifrostConsumerKeyMissing (server misconfig) -> 400. 5 new web bind tests (server constructs binding + key-never-leaks + upstream carries bifrost body + consumer-key bearer; unconfigured -> 400; invalid plane; handshake 502; no-plane unbound regression). Full suite 470 green; added lines ruff + mypy clean (pre-existing web-file backlog untouched). Follow-on: the index.html plane selector (UI trigger) — the server capability is complete and TDD'd; the browser-side dropdown is a thin separate change. LIVE-SMOKE PROVEN (this session): the CLI bind drove a bound sindra session against personal Worldtree :8081 -> handshake 200 -> the op-feed captured 2 recall searches correlated to the EXACT bound session_id (2c0c7482), with the real #297/#298 union-recall scopes. Bind + observe proven end-to-end live.