Class: Woods::Console::StubBridge
- Inherits:
-
Object
- Object
- Woods::Console::StubBridge
- Defined in:
- lib/woods/console/bridge.rb
Overview
PROTOCOL SCAFFOLD — does not execute real queries. Every handler
below returns static empty data ({ 'count' => 0 }, { 'records' => [] }, etc.). Real in-process execution lives in
EmbeddedExecutor; the eventual real bridge process
for Option D will replace this scaffold with a class that performs
actual ActiveRecord queries.
The scaffold pins the JSON-lines wire protocol — request envelope, response envelope, supported-tools list, error shape — so other components (EmbeddedExecutor, ConnectionManager, Server) can be built and tested against a stable contract before the real bridge-process implementation lands. Treat this class the way you'd treat a Sinatra fake of a third-party API in tests: it satisfies the protocol, nothing more.
Why the name carries "Stub"
Round-1 audit Track H-4 flagged this class as a "critical SafeContext
bypass" because handle_request doesn't wrap calls in SafeContext.
That finding wasn't exploitable — no live code path executes through
this class in the shipped gem — but the bare name Bridge made the
scaffold status invisible to auditors. The Stub prefix removes the
ambiguity. When the real bridge-process implementation is delivered,
it should claim the Bridge name; this class will either be deleted
(if the protocol is fully owned by the real bridge) or renamed to
BridgeProtocol and reduced to a constants module.
Protocol
Reads JSON-lines requests from an input IO, validates model/column names, dispatches to (stub) tool handlers, and writes JSON-lines responses to an output IO.
Protocol:
Request: {"id":"req_1","tool":"count","params":{"model":"Order","scope":{"status":"pending"}}}
Response: {"id":"req_1","ok":true,"result":{"count":1847},"timing_ms":12.3}
Error: {"id":"req_1","ok":false,"error":"Model not found","error_type":"validation"}
Constant Summary collapse
- SUPPORTED_TOOLS =
Protocol constants live on BridgeProtocol so the real executor (EmbeddedExecutor) and a future real bridge-process class can reference them without importing the scaffold. These top-level aliases keep
StubBridge::SUPPORTED_TOOLSworking for existing callers and specs. BridgeProtocol::SUPPORTED_TOOLS
- TIER1_TOOLS =
BridgeProtocol::TIER1_TOOLS
- TOOL_HANDLERS =
BridgeProtocol::TOOL_HANDLERS
Instance Method Summary collapse
-
#handle_request(request) ⇒ Hash
Process a single request hash and return a response hash.
-
#initialize(input:, output:, model_validator:, safe_context:) ⇒ StubBridge
constructor
A new instance of StubBridge.
-
#run ⇒ void
Read loop — processes requests until input is closed.
Constructor Details
#initialize(input:, output:, model_validator:, safe_context:) ⇒ StubBridge
Returns a new instance of StubBridge.
67 68 69 70 71 72 |
# File 'lib/woods/console/bridge.rb', line 67 def initialize(input:, output:, model_validator:, safe_context:) @input = input @output = output @model_validator = model_validator @safe_context = safe_context end |
Instance Method Details
#handle_request(request) ⇒ Hash
Process a single request hash and return a response hash.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/woods/console/bridge.rb', line 94 def handle_request(request) id = request['id'] tool = request['tool'] params = request['params'] || {} return error_response(id, "Unknown tool: #{tool}", 'unknown_tool') unless SUPPORTED_TOOLS.include?(tool) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = dispatch(tool, params) elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(1) { 'id' => id, 'ok' => true, 'result' => result, 'timing_ms' => elapsed } rescue ValidationError => e error_response(id, e., 'validation') rescue StandardError => e error_response(id, e., 'execution') end |
#run ⇒ void
This method returns an undefined value.
Read loop — processes requests until input is closed.
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/woods/console/bridge.rb', line 77 def run @input.each_line do |line| line = line.strip next if line.empty? request = parse_request(line) next unless request response = handle_request(request) write_response(response) end end |