Class: KairosMcp::Storage::FileBackend
- Defined in:
- lib/kairos_mcp/storage/file_backend.rb
Overview
File-based storage backend (default)
This is the default storage backend for KairosChain, suitable for individual use. Data is stored in JSON/JSONL files.
Storage locations:
- Blockchain: storage/blockchain.json
- Action logs: skills/action_log.jsonl
- Knowledge metadata: extracted from *.md files (no separate storage)
Instance Attribute Summary collapse
-
#action_log_file ⇒ Object
readonly
Get the blockchain file path (for compatibility).
-
#blockchain_file ⇒ Object
readonly
Get the blockchain file path (for compatibility).
-
#storage_dir ⇒ Object
readonly
Get the blockchain file path (for compatibility).
Instance Method Summary collapse
- #action_history(limit: 50) ⇒ Object
- #all_blocks ⇒ Object
- #backend_type ⇒ Object
- #clear_action_log! ⇒ Object
- #delete_knowledge_meta(_name) ⇒ Object
- #get_knowledge_meta(_name) ⇒ Object
-
#initialize(config = {}) ⇒ FileBackend
constructor
A new instance of FileBackend.
- #list_knowledge_meta ⇒ Object
-
#load_blocks ⇒ Object
Read contract (see Backend#load_blocks): nil means the ledger does not exist, and nothing else.
-
#ready? ⇒ Boolean
Utility Methods ===========================================================================.
-
#record_action(entry) ⇒ Object
Action Log Operations ===========================================================================.
-
#save_all_blocks(blocks) ⇒ Object
Write contract (see Backend#save_all_blocks): failure raises Storage::Error.
- #save_block(block) ⇒ Object
-
#save_knowledge_meta(_name, _meta) ⇒ Object
Knowledge Meta Operations =========================================================================== For FileBackend, metadata is not stored separately.
- #update_knowledge_archived(_name, _archived, reason: nil) ⇒ Object
Methods inherited from Backend
create, default, load_config, register, unregister
Constructor Details
#initialize(config = {}) ⇒ FileBackend
Returns a new instance of FileBackend.
22 23 24 25 26 27 28 29 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 22 def initialize(config = {}) @storage_dir = config[:storage_dir] || KairosMcp.storage_dir @blockchain_file = config[:blockchain_file] || KairosMcp.blockchain_path @action_log_file = config[:action_log_file] || KairosMcp.action_log_path FileUtils.mkdir_p(@storage_dir) FileUtils.mkdir_p(File.dirname(@action_log_file)) end |
Instance Attribute Details
#action_log_file ⇒ Object (readonly)
Get the blockchain file path (for compatibility)
183 184 185 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 183 def action_log_file @action_log_file end |
#blockchain_file ⇒ Object (readonly)
Get the blockchain file path (for compatibility)
183 184 185 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 183 def blockchain_file @blockchain_file end |
#storage_dir ⇒ Object (readonly)
Get the blockchain file path (for compatibility)
183 184 185 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 183 def storage_dir @storage_dir end |
Instance Method Details
#action_history(limit: 50) ⇒ Object
122 123 124 125 126 127 128 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 122 def action_history(limit: 50) return [] unless File.exist?(@action_log_file) File.readlines(@action_log_file) .last(limit) .filter_map { |line| JSON.parse(line, symbolize_names: true) rescue nil } end |
#all_blocks ⇒ Object
98 99 100 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 98 def all_blocks load_blocks || [] end |
#backend_type ⇒ Object
178 179 180 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 178 def backend_type :file end |
#clear_action_log! ⇒ Object
130 131 132 133 134 135 136 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 130 def clear_action_log! File.write(@action_log_file, '') true rescue StandardError => e warn "[FileBackend] Failed to clear action log: #{e.}" false end |
#delete_knowledge_meta(_name) ⇒ Object
160 161 162 163 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 160 def (_name) # No-op for file backend true end |
#get_knowledge_meta(_name) ⇒ Object
150 151 152 153 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 150 def (_name) # No separate metadata storage - return nil nil end |
#list_knowledge_meta ⇒ Object
155 156 157 158 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 155 def # No separate metadata storage - return empty array [] end |
#load_blocks ⇒ Object
Read contract (see Backend#load_blocks): nil means the ledger does not exist, and nothing else. A ledger that exists but cannot be opened or parsed — a zero-byte file included — raises Storage::Error. Returning nil there would make a damaged ledger indistinguishable from a fresh install, and the next append would rebuild from genesis over it.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 40 def load_blocks # Absence is decided by stat, not by File.exist?. File.exist? answers # false for EVERY stat(2) failure, not only ENOENT — an unsearchable # parent directory, a symlink loop, EIO on a network mount, or a macOS # ACL denying readattr all read as "no ledger here". Measured on darwin # 23.6: with `chmod +a "user deny readattr"` on the ledger, File.exist? # is false while File.read and File.write both still succeed, so the # ledger classified :absent and the next append rebuilt from genesis # over it — 6 blocks became 2. Only ENOENT and ENOTDIR mean the ledger # is not there; every other stat failure means we cannot tell. begin File.stat(@blockchain_file) rescue Errno::ENOENT, Errno::ENOTDIR return nil rescue SystemCallError => e raise Storage::Error, "cannot determine whether ledger #{@blockchain_file} exists: #{e.}" end # FIX E — NEW CLAIM: the bytes on disk alone decide the classification; # the process locale never does. The ledger is always written as UTF-8, # but File.read without an encoding tags the bytes with the locale-derived # default. Started with LANG unset (launchd, cron, plain containers), a # ledger holding non-ASCII text then fails the parse and a healthy 705-block # ledger reads :corrupt (measured on a copy of a production ledger; the # shipped pre-fix code went further and erased it to 2 blocks on the next # append). The bytes were never wrong — only the read was. json_data = JSON.parse(File.read(@blockchain_file, encoding: Encoding::UTF_8), symbolize_names: true) unless json_data.is_a?(Array) raise Storage::Error, "ledger #{@blockchain_file} is not a JSON array (got #{json_data.class})" end json_data.map do |block_data| normalize_block_data(block_data) end rescue Storage::Error raise rescue JSON::ParserError, ArgumentError, SystemCallError, IOError, NoMethodError, TypeError => e raise Storage::Error, "failed to load blocks from #{@blockchain_file}: #{e.}" end |
#ready? ⇒ Boolean
===========================================================================
Utility Methods
174 175 176 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 174 def ready? true end |
#record_action(entry) ⇒ Object
===========================================================================
Action Log Operations
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 106 def record_action(entry) normalized = { timestamp: entry[:timestamp] || Time.now.iso8601, action: entry[:action], skill_id: entry[:skill_id], details: entry[:details] } FileUtils.mkdir_p(File.dirname(@action_log_file)) File.open(@action_log_file, 'a') { |f| f.puts(normalized.to_json) } true rescue StandardError => e warn "[FileBackend] Failed to record action: #{e.}" false end |
#save_all_blocks(blocks) ⇒ Object
Write contract (see Backend#save_all_blocks): failure raises
Storage::Error. The previous false return collapsed every failure into
a value that reads as benign at the call site.
90 91 92 93 94 95 96 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 90 def save_all_blocks(blocks) FileUtils.mkdir_p(File.dirname(@blockchain_file)) File.write(@blockchain_file, JSON.pretty_generate(blocks.map { |b| block_to_hash(b) })) true rescue StandardError => e raise Storage::Error, "failed to save blocks to #{@blockchain_file}: #{e.}" end |
#save_block(block) ⇒ Object
81 82 83 84 85 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 81 def save_block(block) blocks = load_blocks || [] blocks << block_to_hash(block) save_all_blocks(blocks) end |
#save_knowledge_meta(_name, _meta) ⇒ Object
===========================================================================
Knowledge Meta Operations
For FileBackend, metadata is not stored separately. These methods are no-ops or return empty results. The actual metadata is extracted from *.md files by KnowledgeProvider.
145 146 147 148 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 145 def (_name, ) # No-op for file backend - metadata is in the files true end |
#update_knowledge_archived(_name, _archived, reason: nil) ⇒ Object
165 166 167 168 |
# File 'lib/kairos_mcp/storage/file_backend.rb', line 165 def update_knowledge_archived(_name, _archived, reason: nil) # No-op for file backend - archiving is folder-based true end |