Class: SmartBrain::Governance::Lifecycle

Inherits:
Object
  • Object
show all
Defined in:
lib/smart_brain/governance/lifecycle.rb

Overview

Stage-1 knowledge lifecycle (mempal parity):

evidence → distill(candidate) → gate → promote(promoted) → demote/retire

Operates over a MemoryStore (independent of backend). distill creates a candidate knowledge item at tier dao_ren|qi; gate is a read-only readiness check; promote/demote are evidence-backed transitions that append to the knowledge_events audit trail. Orthogonal to the conflict status.

Constant Summary collapse

DEMOTE_REASONS =
%w[contradicted obsolete superseded].freeze

Instance Method Summary collapse

Constructor Details

#initialize(memory_store:, config:, model_provider: nil) ⇒ Lifecycle

Returns a new instance of Lifecycle.



19
20
21
22
23
# File 'lib/smart_brain/governance/lifecycle.rb', line 19

def initialize(memory_store:, config:, model_provider: nil)
  @memory_store = memory_store
  @config = config
  @model_provider = model_provider
end

Instance Method Details

#demote(memory_item_id:, evidence_refs:, reason:, reason_type:, reviewer: nil) ⇒ Object

Raises:

  • (ArgumentError)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/smart_brain/governance/lifecycle.rb', line 101

def demote(memory_item_id:, evidence_refs:, reason:, reason_type:, reviewer: nil)
  raise ArgumentError, "reason_type must be one of #{DEMOTE_REASONS.join('/')}" unless DEMOTE_REASONS.include?(reason_type.to_s)

  item = memory_store.find_item(id: memory_item_id)
  raise ArgumentError, "memory item not found: #{memory_item_id}" unless item

  from = item[:lifecycle_status]
  updated = memory_store.set_lifecycle(
    id: memory_item_id, lifecycle_status: 'demoted',
    merge_value: { demotion_reason: reason, reason_type: reason_type }
  )
  memory_store.record_event(
    memory_item_id: memory_item_id, event_type: 'demote',
    from_lifecycle: from, to_lifecycle: 'demoted',
    reason: reason, reason_type: reason_type, reviewer: reviewer, evidence_refs: Array(evidence_refs)
  )
  { memory_item: updated, event: last_event(memory_item_id) }
end

#distill(session_id:, statement:, content: nil, tier:, supporting_refs: [], domain: nil, field: nil, reviewer: nil, key: nil, scope_id: nil, scope: nil) ⇒ Object

Raises:

  • (ArgumentError)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/smart_brain/governance/lifecycle.rb', line 25

def distill(session_id:, statement:, content: nil, tier:, supporting_refs: [], domain: nil, field: nil,
            reviewer: nil, key: nil, scope_id: nil, scope: nil)
  tier = tier.to_s
  raise ArgumentError, "tier must be one of #{Tiers::DISTILLABLE.join('/')}" unless Tiers.distillable?(tier)

  statement = draft_statement(statement, supporting_refs: supporting_refs, content: content) if statement.nil? || statement.strip.empty?
  raise ArgumentError, 'statement is required (provide one or wire an LLM provider)' if statement.nil? || statement.strip.empty?

  item = {
    type: 'knowledge',
    key: key || "knowledge:#{tier}:#{slugify(statement)}",
    value_json: {
      statement: statement,
      content: content,
      tier: tier,
      domain: domain,
      field: field,
      supporting_refs: Array(supporting_refs)
    },
    confidence: 0.5,
    status: 'active',
    tier: tier,
    lifecycle_status: 'candidate',
    source_turn_id: nil,
    updated_at: Time.now.utc.iso8601
  }
  record = memory_store.create_item(session_id: session_id, item: item, scope_id: scope_id, scope: scope)
  memory_store.record_event(
    memory_item_id: record[:id], event_type: 'distill',
    from_lifecycle: nil, to_lifecycle: 'candidate',
    reason: 'distilled from evidence', reviewer: reviewer,
    evidence_refs: Array(supporting_refs)
  )
  { memory_item: record, event: last_event(record[:id]) }
end

#events(memory_item_id:) ⇒ Object



184
185
186
# File 'lib/smart_brain/governance/lifecycle.rb', line 184

def events(memory_item_id:)
  memory_store.events_for(memory_item_id: memory_item_id)
end

#gate(memory_item_id:) ⇒ Object

Read-only promotion readiness check.

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/smart_brain/governance/lifecycle.rb', line 62

def gate(memory_item_id:)
  item = memory_store.find_item(id: memory_item_id)
  raise ArgumentError, "memory item not found: #{memory_item_id}" unless item

  refs = Array(item.dig(:value_json, :supporting_refs) || item.dig(:value_json, 'supporting_refs'))
  min = min_supporting_refs
  reviewer = item.dig(:value_json, :reviewer) || item.dig(:value_json, 'reviewer')
  checks = {
    lifecycle: item[:lifecycle_status],
    supporting_refs: refs.size,
    min_required: min,
    reviewer: reviewer
  }
  ready = item[:lifecycle_status] == 'candidate' && (refs.size >= min || reviewer == 'human')
  { ready: ready, target_status: 'promoted', checks: checks }
end

#lineage(memory_item_id:) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/smart_brain/governance/lifecycle.rb', line 171

def lineage(memory_item_id:)
  lineage = []
  current = memory_store.find_item(id: memory_item_id)
  while current
    lineage << current
    source_id = current.dig(:value_json, :provenance, :source_memory_item_id)
    break unless source_id

    current = memory_store.find_item(id: source_id)
  end
  lineage
end

#promote(memory_item_id:, verification_refs:, reason:, reviewer: nil, force: false) ⇒ Object

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/smart_brain/governance/lifecycle.rb', line 79

def promote(memory_item_id:, verification_refs:, reason:, reviewer: nil, force: false)
  item = memory_store.find_item(id: memory_item_id)
  raise ArgumentError, "memory item not found: #{memory_item_id}" unless item

  decision = gate(memory_item_id: memory_item_id)
  if !decision[:ready] && !force
    raise LifecycleGateError, "promotion gate failed: #{decision[:checks].inspect}"
  end

  from = item[:lifecycle_status]
  updated = memory_store.set_lifecycle(
    id: memory_item_id, lifecycle_status: 'promoted',
    merge_value: { verification_refs: Array(verification_refs), reviewer: reviewer }
  )
  memory_store.record_event(
    memory_item_id: memory_item_id, event_type: 'promote',
    from_lifecycle: from, to_lifecycle: 'promoted',
    reason: reason, reviewer: reviewer, evidence_refs: Array(verification_refs)
  )
  { memory_item: updated, event: last_event(memory_item_id) }
end

#promote_to_scope(source_item:, target_scope:, session_id:, verification_refs:, reason:, reviewer: nil) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/smart_brain/governance/lifecycle.rb', line 120

def promote_to_scope(source_item:, target_scope:, session_id:, verification_refs:, reason:, reviewer: nil)
  refs = Array(verification_refs)
  unless reviewer == 'human' || refs.size >= min_supporting_refs
    raise LifecycleGateError, "scope promotion requires human review or #{min_supporting_refs} verification refs"
  end

  provenance = {
    source_memory_item_id: source_item[:id],
    source_scope: source_item[:scope],
    target_scope: { type: target_scope[:type], id: target_scope[:id] },
    source_session_id: source_item[:source_session_id] || source_item[:session_id],
    verification_refs: refs,
    reviewer: reviewer,
    reason: reason
  }
  item = source_item.slice(:type, :key, :confidence, :tier).merge(
    value_json: (source_item[:value_json] || {}).merge(provenance: provenance),
    status: 'active', lifecycle_status: 'promoted', source_turn_id: source_item[:source_turn_id],
    source_message_id: source_item[:source_message_id],
    source_session_id: source_item[:source_session_id] || source_item[:session_id],
    updated_at: Time.now.utc.iso8601
  )
  target = memory_store.create_item(
    session_id: session_id, item: item, scope_id: target_scope[:scope_id],
    scope: { type: target_scope[:type], id: target_scope[:id] }
  )
  event = memory_store.record_event(
    memory_item_id: target[:id], event_type: 'promote_to_scope',
    from_lifecycle: source_item[:lifecycle_status], to_lifecycle: 'promoted',
    reason: reason, reviewer: reviewer, evidence_refs: refs
  )
  target = memory_store.set_lifecycle(
    id: target[:id], lifecycle_status: 'promoted',
    merge_value: { provenance: provenance.merge(promotion_event_id: event[:id]) }
  )
  { source_memory_item: source_item, memory_item: target, event: event }
end

#retract(memory_item_id:, evidence_refs:, reason:, reviewer: nil) ⇒ Object

Raises:

  • (ArgumentError)


158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/smart_brain/governance/lifecycle.rb', line 158

def retract(memory_item_id:, evidence_refs:, reason:, reviewer: nil)
  item = memory_store.find_item(id: memory_item_id)
  raise ArgumentError, "memory item not found: #{memory_item_id}" unless item

  updated = memory_store.set_status(id: memory_item_id, status: 'retracted', merge_value: { retraction_reason: reason })
  event = memory_store.record_event(
    memory_item_id: memory_item_id, event_type: 'retract',
    from_lifecycle: item[:lifecycle_status], to_lifecycle: item[:lifecycle_status],
    reason: reason, reviewer: reviewer, evidence_refs: Array(evidence_refs)
  )
  { memory_item: updated, event: event }
end