Class: OKF::MCP::Registry
- Inherits:
-
Object
- Object
- OKF::MCP::Registry
- Defined in:
- lib/okf/mcp/registry.rb
Overview
The allowlist of bundles the server may touch, and the one place bundle
identity is decided. Every tool takes a bundle argument that is a
registry slug — the same identity @slug resolves at the CLI and
/b/<slug>/ mounts on the hub — and that slug is only ever a key into
this map: no tool opens an arbitrary path from a request.
Two boots. Argv roots are the allowlist: okf-mcp <dir> [<dir>…] serves
exactly those, slugged by the kernel's own normalization (a leading @
names a registered bundle or group, whose slug is reserved before any
plain-dir basename is deduped — the server verb's rule). No argv means
the active kernel registry, resolved exactly as the CLI resolves it:
a project-local .okf-registry.json discovered from cwd, else
$OKF_HOME/registry.json, OKF_NO_DISCOVERY=1 forcing global.
Defined Under Namespace
Classes: Entry
Instance Attribute Summary collapse
-
#source ⇒ Object
readonly
Returns the value of attribute source.
Class Method Summary collapse
-
.from_argv(args) ⇒ Object
Boot from explicit argv roots — directories and @refs, mixed.
-
.from_kernel ⇒ Object
Boot from the active kernel registry — the no-argv default.
Instance Method Summary collapse
-
#boot_notes ⇒ Object
Boot-time skips (a group member whose directory is gone) for the CLI to print; tools never see them.
-
#default_slug ⇒ Object
The kernel's rule: the first entry still on disk, else the first.
-
#entries ⇒ Object
The served set, re-read when the registry file moves underneath it.
- #group?(slug) ⇒ Boolean
-
#groups ⇒ Object
The registered groups, for list_bundles — [] when the allowlist came from argv, where groups fanned out to their leaves at boot.
-
#initialize(entries, kernel: nil, source: nil, notes: []) ⇒ Registry
constructor
kernelstays only for what an allowlist cannot answer alone — group membership — andsourcenames the registry file the boot line reports. -
#resolve_search(asked) ⇒ Object
What
searchshould read: nil or "*" is every served bundle, a slug is that bundle, a group slug (registry mode) is its bundle leaves, an array mixes them — deduped by root. -
#root!(bundle) ⇒ Object
The root behind one named slug — the resolution every single-bundle tool goes through.
- #slugs ⇒ Object
Constructor Details
#initialize(entries, kernel: nil, source: nil, notes: []) ⇒ Registry
kernel stays only for what an allowlist cannot answer alone — group
membership — and source names the registry file the boot line reports.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/okf/mcp/registry.rb', line 142 def initialize(entries, kernel: nil, source: nil, notes: []) @entries = entries @kernel = kernel @source = source @notes = notes @mutex = Mutex.new # Deliberately *not* stamped here. A stamp is a claim about a file this # instance has already read, and the boot read happened before this # constructor ran — so a stat taken now would record a write that # landed in between as already-seen: entries from before it, fingerprint # from after, and #refresh! with nothing to do until some further write # moves the fingerprint again. Resolving the path to stat it ahead of # the read would mean reimplementing the kernel's discovery precedence # here, so the first #refresh! re-reads instead — one parse, once per # process, in exchange for never holding a fingerprint the entries do # not match. #refresh! itself already stats before it reopens. @stamp = nil end |
Instance Attribute Details
#source ⇒ Object (readonly)
Returns the value of attribute source.
161 162 163 |
# File 'lib/okf/mcp/registry.rb', line 161 def source @source end |
Class Method Details
.from_argv(args) ⇒ Object
Boot from explicit argv roots — directories and @refs, mixed. Raises Error on anything unresolvable; vanished group members are skipped with a note in #boot_notes (asking for a set tolerates gaps, naming one bundle demands it).
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/okf/mcp/registry.rb', line 30 def from_argv(args) notes = [] kernel = nil ref_slugs = {} roots = [] Array(args).each do |arg| kernel ||= kernel_registry if arg.start_with?("@") resolve_arg(arg, kernel, ref_slugs, notes).each do |root| roots << root unless roots.include?(root) end end raise Error, "no bundle roots given" if roots.empty? # The kernel registry is deliberately **not** carried into the # instance. It answered the @refs above, at boot, where the operator # asked for them — keeping it would leave a request-time door into # every bundle argv did not name: a group slug passed to `search` # expanded through it and returned content from bundles outside the # allowlist. Argv names the served set; groups are a registry-mode # identity, and in argv mode they already fanned out to their leaves. new(mint_entries(roots, ref_slugs), source: kernel&.path, notes: notes) end |
.from_kernel ⇒ Object
Boot from the active kernel registry — the no-argv default.
54 55 56 57 58 59 60 61 62 |
# File 'lib/okf/mcp/registry.rb', line 54 def from_kernel kernel = kernel_registry if kernel.empty? raise Error, "no bundle roots given and no bundles registered — run `okf registry set <dir>` (registry: #{kernel.path})" end entries = kernel.map { |entry| Entry.new(entry.slug, entry.path, entry.title) } new(entries, kernel: kernel, source: kernel.path, notes: []) end |
Instance Method Details
#boot_notes ⇒ Object
Boot-time skips (a group member whose directory is gone) for the CLI to print; tools never see them.
188 189 190 |
# File 'lib/okf/mcp/registry.rb', line 188 def boot_notes @notes end |
#default_slug ⇒ Object
The kernel's rule: the first entry still on disk, else the first.
197 198 199 200 201 |
# File 'lib/okf/mcp/registry.rb', line 197 def default_slug rows = entries chosen = rows.find { |entry| File.directory?(entry.root) } || rows.first chosen&.slug end |
#entries ⇒ Object
The served set, re-read when the registry file moves underneath it.
This is the same rule the residency layer applies to bundle contents,
applied to the identity map — one stat, and a parse only when the
fingerprint has changed. It was a boot snapshot, which the kernel's own
hub also is for what it mounts; three of the four ways that went stale
were loud (an unknown slug names what it knows), but the fourth was
not: an entry repointed at a new directory kept answering from the old
one under the current slug, which is a silent wrong answer.
Argv mode is untouched, and not by a flag: it never carried the kernel registry into the instance, so there is nothing here to re-read and no way for the served set to widen. Containment stays a property of the shape.
177 178 179 180 |
# File 'lib/okf/mcp/registry.rb', line 177 def entries refresh! @entries end |
#group?(slug) ⇒ Boolean
210 211 212 213 214 215 |
# File 'lib/okf/mcp/registry.rb', line 210 def group?(slug) refresh! return false if @kernel.nil? !@kernel.group?(slug).nil? end |
#groups ⇒ Object
The registered groups, for list_bundles — [] when the allowlist came from argv, where groups fanned out to their leaves at boot.
205 206 207 208 |
# File 'lib/okf/mcp/registry.rb', line 205 def groups refresh! @kernel.nil? ? [] : @kernel.groups_listing end |
#resolve_search(asked) ⇒ Object
What search should read: nil or "" is every served bundle, a slug is
that bundle, a group slug (registry mode) is its bundle leaves, an array
mixes them — deduped by root. Returns [ pairs, skipped ]: pairs as
[ slug, root ], skipped naming the bundles ""/a group forgave (their
directory is gone); a named slug whose directory is gone raises.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/okf/mcp/registry.rb', line 238 def resolve_search(asked) # An *empty list* is an argument mistake, not a fact about the disk. It # used to fall through to the "missing on disk" branch below, sending # the reader off diagnosing a broken installation. raise Error, "bundle: [] names no bundle — omit it (or pass \"*\") to search every bundle" if asked.is_a?(Array) && asked.empty? names = Array(OKF.blank?(asked) ? "*" : asked) pairs = [] skipped = [] names.each do |name| resolve_search_name(name.to_s, pairs, skipped) end raise Error, "every requested bundle is missing on disk (okf registry list)" if pairs.empty? [ pairs, skipped.uniq ] end |
#root!(bundle) ⇒ Object
The root behind one named slug — the resolution every single-bundle tool goes through. Naming demands: an unknown slug, a group, and a vanished directory are each a hard, actionable error.
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/okf/mcp/registry.rb', line 220 def root!(bundle) slug = OKF::Registry.normalize(bundle) entry = entries.find { |candidate| candidate.slug == slug } if entry.nil? raise group_error(slug) if group?(slug) raise Error, "unknown bundle #{bundle.inspect} — known: #{slugs.join(", ")}" end raise Error, "bundle #{slug} is registered at #{entry.root}, which is not a directory (okf registry list)" unless File.directory?(entry.root) entry.root end |
#slugs ⇒ Object
192 193 194 |
# File 'lib/okf/mcp/registry.rb', line 192 def slugs entries.map(&:slug) end |