Class: Woods::Console::StubBridge

Inherits:
Object
  • Object
show all
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"}

Examples:

Wiring against a fake input/output (testing only — handlers return empty data)

bridge = StubBridge.new(input: $stdin, output: $stdout,
                        model_validator: validator, safe_context: ctx)
bridge.run

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

Constructor Details

#initialize(input:, output:, model_validator:, safe_context:) ⇒ StubBridge

Returns a new instance of StubBridge.

Parameters:

  • input (IO)

    Input stream (reads JSON-lines)

  • output (IO)

    Output stream (writes JSON-lines)

  • model_validator (ModelValidator)

    Validates model/column names

  • safe_context (SafeContext)

    Wraps execution in safe transaction



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.

Parameters:

  • request (Hash)

    Parsed request with “id”, “tool”, “params”

Returns:

  • (Hash)

    Response with “id”, “ok”, and “result” or “error”



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.message, 'validation')
rescue StandardError => e
  error_response(id, e.message, 'execution')
end

#runvoid

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