Class: RosettAi::Provenance::Store
- Inherits:
-
Object
- Object
- RosettAi::Provenance::Store
- Defined in:
- lib/rosett_ai/provenance/store.rb
Overview
Append-only YAML store for .ai-provenance.yml.
Manages the provenance file lifecycle: creation, appending entries, reading, querying, and hash chain integrity. Entries are never modified or deleted. Each entry includes a SHA-256 hash of the previous entry to create a tamper-evident chain.
Constant Summary collapse
- FILENAME =
'.ai-provenance.yml'- MAX_FILE_SIZE =
1 MB
1_048_576- SCHEMA_VERSION =
1- ZERO_HASH =
('0' * 64).freeze
Instance Attribute Summary collapse
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Instance Method Summary collapse
-
#append(entry) ⇒ Pathname
Appends an entry to the provenance file with hash chain linkage.
-
#entries_for_commit(commit_sha) ⇒ Array<Hash>
Returns all entries for a specific commit.
-
#entries_for_file(file_path) ⇒ Array<Hash>
Returns all entries that reference a specific file.
-
#entry_count ⇒ Integer
Number of entries in the provenance file.
-
#exist? ⇒ Boolean
True if the provenance file exists.
-
#init ⇒ Pathname
Creates a new provenance file with version header.
-
#initialize(root:) ⇒ Store
constructor
A new instance of Store.
-
#path ⇒ Pathname
Full path to the provenance file.
-
#read ⇒ Hash
Reads and parses the provenance file.
-
#verify_chain ⇒ Array<String>
Verifies the integrity of the hash chain.
Constructor Details
Instance Attribute Details
#root ⇒ Object (readonly)
Returns the value of attribute root.
26 27 28 |
# File 'lib/rosett_ai/provenance/store.rb', line 26 def root @root end |
Instance Method Details
#append(entry) ⇒ Pathname
Appends an entry to the provenance file with hash chain linkage.
60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/rosett_ai/provenance/store.rb', line 60 def append(entry) raise RosettAi::ProvenanceError, "Provenance file not found: #{path}" unless exist? warn_file_size if file_over_limit? data = read_data entry_hash = chain_hash(data['entries']) serialized = entry.to_h.merge('chain_hash' => entry_hash) data['entries'] << serialized path.write(YAML.dump(data)) path end |
#entries_for_commit(commit_sha) ⇒ Array<Hash>
Returns all entries for a specific commit.
86 87 88 89 90 |
# File 'lib/rosett_ai/provenance/store.rb', line 86 def entries_for_commit(commit_sha) return [] unless exist? read['entries'].select { |entry| entry['commit']&.start_with?(commit_sha) } end |
#entries_for_file(file_path) ⇒ Array<Hash>
Returns all entries that reference a specific file.
96 97 98 99 100 101 102 |
# File 'lib/rosett_ai/provenance/store.rb', line 96 def entries_for_file(file_path) return [] unless exist? read['entries'].select do |entry| Array(entry['files']).any? { |file| file['path'] == file_path } end end |
#entry_count ⇒ Integer
Returns number of entries in the provenance file.
105 106 107 108 109 |
# File 'lib/rosett_ai/provenance/store.rb', line 105 def entry_count return 0 unless exist? read['entries'].size end |
#exist? ⇒ Boolean
Returns true if the provenance file exists.
39 40 41 |
# File 'lib/rosett_ai/provenance/store.rb', line 39 def exist? path.exist? end |
#init ⇒ Pathname
Creates a new provenance file with version header.
47 48 49 50 51 52 53 |
# File 'lib/rosett_ai/provenance/store.rb', line 47 def init raise RosettAi::ProvenanceError, "Provenance file already exists: #{path}" if exist? data = { 'provenance_version' => SCHEMA_VERSION, 'entries' => [] } path.write(YAML.dump(data)) path end |
#path ⇒ Pathname
Returns full path to the provenance file.
34 35 36 |
# File 'lib/rosett_ai/provenance/store.rb', line 34 def path @root.join(FILENAME) end |
#read ⇒ Hash
Reads and parses the provenance file.
76 77 78 79 80 |
# File 'lib/rosett_ai/provenance/store.rb', line 76 def read raise RosettAi::ProvenanceError, "Provenance file not found: #{path}" unless exist? read_data end |
#verify_chain ⇒ Array<String>
Verifies the integrity of the hash chain.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/rosett_ai/provenance/store.rb', line 114 def verify_chain return [] unless exist? entries = read['entries'] violations = [] entries.each_with_index do |entry, index| expected = chain_hash(entries[0...index]) actual = entry['chain_hash'] next if actual == expected violations << "Entry ##{index + 1} (#{entry['commit']}): " \ "expected #{expected[0, 16]}..., got #{(actual || 'nil')[0, 16]}..." end violations end |