Class: KairosMcp::DriftDetection::CorrespondenceChecker
- Inherits:
-
Object
- Object
- KairosMcp::DriftDetection::CorrespondenceChecker
- Defined in:
- lib/kairos_mcp/drift_detection/correspondence_checker.rb
Overview
CorrespondenceChecker — INV-A detection floor (Cycle 1, toward by-construction).
Checks whether a live L0/L1 artifact still corresponds to its current recorded provenance: the content digest stored for that artifact at the head of the constitutive record (the hash chain). This is detection only — it surfaces divergence; it does not prevent edits or gate writes. Those are later cycles (single-source enforcement, record-as-gate).
Provenance is rooted in the hash chain, not in the SQLite knowledge_meta cache: INV-A names the chain head as the non-editable anchor. The chain is therefore the single source consulted here; the meta table (when present) is a derived view and is intentionally not used for the comparison.
The digest is computed over the raw file content (frontmatter included), matching exactly how it was recorded on create/update (a verbatim write, no normalization). Comparing the parsed/stripped body would never match.
Defined Under Namespace
Classes: Result
Class Method Summary collapse
-
.check_l1(name:, md_file_path:, storage_backend: nil) ⇒ Result
Check an L1 knowledge artifact against its recorded provenance.
Class Method Details
.check_l1(name:, md_file_path:, storage_backend: nil) ⇒ Result
Check an L1 knowledge artifact against its recorded provenance.
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/kairos_mcp/drift_detection/correspondence_checker.rb', line 55 def check_l1(name:, md_file_path:, storage_backend: nil) unless md_file_path && File.file?(md_file_path) # Relied upon but absent — a missing artifact is itself a # non-correspondence under INV-A (the expected set is recorded). return Result.new( status: :missing_artifact, name: name, active_digest: nil, recorded_digest: nil, message: "L1 '#{name}': artifact missing at the point of reliance" ) end active = Digest::SHA256.hexdigest(File.read(md_file_path)) chain = KairosChain::Chain.new(storage_backend: storage_backend) # :absent completes normally: a fresh install has no ledger, the scan # over zero blocks finds nothing, and :missing_record is the honest # verdict. The other failed states mean provenance is UNAVAILABLE, # which is not the same claim as "never recorded" — reporting # :missing_record there would accuse every artifact whenever the # ledger cannot be read. unless chain.valid? || chain.load_state == :absent return Result.new( status: :error, name: name, active_digest: active, recorded_digest: nil, message: "L1 '#{name}': provenance unavailable — ledger state is #{chain.load_state}" ) end recorded = recorded_digest_for(name, chain) if recorded.nil? return Result.new( status: :missing_record, name: name, active_digest: active, recorded_digest: nil, message: "L1 '#{name}': live artifact has no recorded provenance on the chain" ) end if active == recorded Result.new( status: :match, name: name, active_digest: active, recorded_digest: recorded, message: nil ) else Result.new( status: :mismatch, name: name, active_digest: active, recorded_digest: recorded, message: "L1 '#{name}': live content diverged from recorded provenance " \ "(active #{short(active)} ≠ recorded #{short(recorded)})" ) end rescue StandardError => e Result.new( status: :error, name: name, active_digest: nil, recorded_digest: nil, message: "L1 '#{name}': correspondence check could not complete: #{e.}" ) end |