Class: Woods::Console::ResponseContext

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/console/response_context.rb

Overview

Bundles the three Console response-safety layers the Server threads through every tool definition:

  • Layer 1 (TableGate) — reject tool calls that touch blocked tables.

  • Layer 2 (CredentialScanner) — redact credential-shaped substrings in the

    final response tree, regardless of where they arrived.
    
  • Layer 3 (SafeContext) — operator-configured column + EAV redaction.

Exposed as tell-don’t-ask commands: #enforce!, #redact, #scan. Callers never need to ask which layers are configured — a NullResponseContext is returned from ResponseContext.build when every layer is absent, and its commands are no-ops that return their input unchanged.

Ordering (applied in Server#send_to_bridge, after the bridge responds):

Layer 3 (columns/EAV) -> Layer 2 (credential scan) -> response emitted.

Layer 1 runs earlier, before tool dispatch.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(safe_ctx:, table_gate:, credential_scanner:) ⇒ ResponseContext

Returns a new instance of ResponseContext.



38
39
40
41
42
# File 'lib/woods/console/response_context.rb', line 38

def initialize(safe_ctx:, table_gate:, credential_scanner:)
  @safe_ctx = safe_ctx
  @table_gate = table_gate
  @credential_scanner = credential_scanner
end

Instance Attribute Details

#credential_scannerObject (readonly)

Returns the value of attribute credential_scanner.



25
26
27
# File 'lib/woods/console/response_context.rb', line 25

def credential_scanner
  @credential_scanner
end

#safe_ctxObject (readonly)

Returns the value of attribute safe_ctx.



25
26
27
# File 'lib/woods/console/response_context.rb', line 25

def safe_ctx
  @safe_ctx
end

#table_gateObject (readonly)

Returns the value of attribute table_gate.



25
26
27
# File 'lib/woods/console/response_context.rb', line 25

def table_gate
  @table_gate
end

Class Method Details

.build(safe_ctx: nil, table_gate: nil, credential_scanner: nil) ⇒ ResponseContext, NullResponseContext

Returns NullResponseContext when every layer is absent so callers never receive nil.

Returns:



29
30
31
32
33
34
35
36
# File 'lib/woods/console/response_context.rb', line 29

def self.build(safe_ctx: nil, table_gate: nil, credential_scanner: nil)
  gate_inactive = table_gate.nil? || !table_gate.active?
  if safe_ctx.nil? && gate_inactive && credential_scanner.nil?
    NullResponseContext.instance
  else
    new(safe_ctx: safe_ctx, table_gate: table_gate, credential_scanner: credential_scanner)
  end
end

Instance Method Details

#enforce!(args) ⇒ Object

Run the Layer 1 blocked-table gate against the arguments a tool was invoked with. Tools may arrive at tables through five different arg shapes — SQL string, model name, raw table, joined associations, or a single association name — so the gate checks every variant that’s present. A no-op when the gate is nil.

Parameters:

  • args (Hash)

    Tool arguments (symbol keys from MCP dispatch)

Raises:



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/woods/console/response_context.rb', line 57

def enforce!(args) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  return unless @table_gate

  @table_gate.check_sql!(args[:sql]) if args[:sql]
  @table_gate.check_model!(args[:model]) if args[:model]
  @table_gate.check_table!(args[:table]) if args[:table]
  @table_gate.check_joins!(args[:model], args[:joins]) if args[:model] && args[:joins]
  return unless args[:model] && args[:association]

  @table_gate.check_association!(args[:model], args[:association])
end

#present?Boolean

True for real response contexts; false for NullResponseContext.

Returns:

  • (Boolean)


45
46
47
# File 'lib/woods/console/response_context.rb', line 45

def present?
  true
end

#redact(result) ⇒ Object

Apply Layer 3 (column + EAV) redaction to a result value. Shape-aware —see Woods::Console::Redactor.apply for the supported envelope keys.

Parameters:

  • result (Object)

Returns:

  • (Object)

    Redacted result, same shape as input.



74
75
76
77
78
# File 'lib/woods/console/response_context.rb', line 74

def redact(result)
  return result unless @safe_ctx

  Redactor.apply(result, @safe_ctx)
end

#scan(value) ⇒ Array(Object, Hash)

Run Layer 2 (credential scanner) over a value and return the scanned form alongside any hit counts. Callers decide whether to log the counts — the context deliberately does not assume access to a logger.

Parameters:

  • value (Object)

Returns:

  • (Array(Object, Hash))
    scanned_value, counts_by_pattern


86
87
88
89
90
# File 'lib/woods/console/response_context.rb', line 86

def scan(value)
  return [value, {}] unless @credential_scanner

  @credential_scanner.scan(value)
end