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_TOOLS` working 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 |