Class: Rigor::Plugin::IoBoundary
- Inherits:
-
Object
- Object
- Rigor::Plugin::IoBoundary
- Defined in:
- lib/rigor/plugin/io_boundary.rb
Overview
Analyzer-side helper plugins go through to read files and (eventually) reach the network. The boundary enforces the active TrustPolicy and accumulates a Cache::Descriptor of every read so plugin contributions stay invalidatable alongside their inputs.
ADR-2 § “Plugin Trust and I/O Policy” is the binding contract. The boundary is not a sandbox: a plugin that uses ‘File.read` directly bypasses everything here, and the ADR explicitly accepts that trade-off. The discipline is: when plugin code goes through this surface, reads stay within the trust scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not invalidate correctly.
Slice 2 ships a minimal surface:
-
‘#read_file(path)` — validates against the policy, returns the file’s contents, and adds a digest-keyed Cache::Descriptor::FileEntry to the boundary’s accumulated descriptor.
-
‘#open_url(url)` — always raises AccessDeniedError while `network_policy` is `:disabled` (the only setting in slice 2). The hook exists so slices 3-6 can layer richer access policy without re-defining the API.
-
‘#cache_descriptor` — flushes the accumulated entries into a fresh Cache::Descriptor for the contribution that built it.
Instance Attribute Summary collapse
-
#plugin_id ⇒ Object
readonly
Returns the value of attribute plugin_id.
-
#policy ⇒ Object
readonly
Returns the value of attribute policy.
Instance Method Summary collapse
-
#cache_descriptor ⇒ Rigor::Cache::Descriptor
Frozen snapshot of every file the boundary has read so far.
-
#initialize(policy:, plugin_id:) ⇒ IoBoundary
constructor
A new instance of IoBoundary.
-
#open_url(url) ⇒ Object
Slice 2 stub: every URL access is denied while ‘network_policy` is `:disabled`.
-
#read_file(path) ⇒ Object
Reads the file at ‘path` after validating it against the policy.
Constructor Details
#initialize(policy:, plugin_id:) ⇒ IoBoundary
Returns a new instance of IoBoundary.
40 41 42 43 44 45 |
# File 'lib/rigor/plugin/io_boundary.rb', line 40 def initialize(policy:, plugin_id:) @policy = policy @plugin_id = plugin_id.to_s.dup.freeze @file_entries = {} @mutex = Mutex.new end |
Instance Attribute Details
#plugin_id ⇒ Object (readonly)
Returns the value of attribute plugin_id.
38 39 40 |
# File 'lib/rigor/plugin/io_boundary.rb', line 38 def plugin_id @plugin_id end |
#policy ⇒ Object (readonly)
Returns the value of attribute policy.
38 39 40 |
# File 'lib/rigor/plugin/io_boundary.rb', line 38 def policy @policy end |
Instance Method Details
#cache_descriptor ⇒ Rigor::Cache::Descriptor
Returns frozen snapshot of every file the boundary has read so far. Calling this multiple times yields equal descriptors; subsequent reads expand the underlying record table.
88 89 90 91 |
# File 'lib/rigor/plugin/io_boundary.rb', line 88 def cache_descriptor entries = @mutex.synchronize { @file_entries.values.dup } Cache::Descriptor.new(files: entries) end |
#open_url(url) ⇒ Object
Slice 2 stub: every URL access is denied while ‘network_policy` is `:disabled`. Slices that need to relax the rule (e.g. for opt-in offline-replay caches) will lift the policy gate; the API does not change.
71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/rigor/plugin/io_boundary.rb', line 71 def open_url(url) unless @policy.network_allowed? raise AccessDeniedError.new( "plugin #{@plugin_id.inspect} cannot open URL #{url.inspect}: " \ "network access is disabled during analysis", reason: :network_disabled, resource: url.to_s ) end raise NotImplementedError, "URL fetch surface is reserved; slice 2 only ships the deny path" end |
#read_file(path) ⇒ Object
Reads the file at ‘path` after validating it against the policy. Raises AccessDeniedError when the path is outside every allowed read root. Records a `:digest` FileEntry so the resulting cache slice invalidates on content change.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/rigor/plugin/io_boundary.rb', line 51 def read_file(path) absolute = File.(path.to_s) unless @policy.allow_read?(absolute) raise AccessDeniedError.new( "plugin #{@plugin_id.inspect} cannot read #{absolute.inspect}: " \ "path is outside the trusted-read scope", reason: :read_outside_scope, resource: absolute ) end contents = File.binread(absolute) record_file_entry(absolute, contents) contents end |