legion-gaia 0.9.62 → 0.9.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/legion/gaia/bond_registry.rb +21 -3
- data/lib/legion/gaia/death_protocol.rb +147 -0
- data/lib/legion/gaia/routes.rb +13 -2
- data/lib/legion/gaia/version.rb +1 -1
- data/lib/legion/gaia.rb +22 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d865d2e228e487153ee4681a3d0a939c83090e236f58e99cff670b351d5cc07e
|
|
4
|
+
data.tar.gz: 84252e97bc38980791c4ac74aaedf1fcc7bc00d6de30c8da7d4fce98d37a89a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b307e1dfcf3cfba5e3e0378209c4d562578de7085753f216ed6a3d45e969b3d0e92724f6e428d6635dad31d7d19bdbf5f451b7f0e21406492dca09b97a0f1e0
|
|
7
|
+
data.tar.gz: 987ad87a47603e4c65cefaad42b26f8817b65154c070040c84936694e7763e62492664c7bb87305d5d03b6e6e6ce4e7d9ab941290f0f41f251011251968811c1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.63] - 2026-07-22
|
|
4
|
+
### Added
|
|
5
|
+
- H8: `DeathProtocol.terminate_bond(identity:, confirm:)` — single entry point for bond termination; walks cascade across all stores (bond, behavioral_synapses, session, audit, attribution, calibration, preferences, memory_traces, predictions, trust); returns proof-of-destruction receipt with per-store results; emits `gaia.bond.terminated` event
|
|
6
|
+
- H8: `BondRegistry.set_bond_state` / `bond_state` / `terminating?` / `terminated?` — explicit lifecycle state management for partner bonds
|
|
7
|
+
- H8 ingress barrier: `ingest`, `observe_interlocutor`, and `record_response_applied` all reject writes for terminating/terminated identities with a log warning and early return
|
|
8
|
+
- H8 irreversibility: `observe_interlocutor` blocks re-registration of terminated identities — a new bond requires explicit `begin_new_imprint` (out of scope)
|
|
9
|
+
- DELETE `/api/gaia/bond/:identity` route — terminates a bond and returns the receipt
|
|
10
|
+
- Soft-guarded erasure helpers for optional stores (calibration, preferences, memory traces, predictions, trust) — skips gracefully when extension not loaded, raises on actual store errors
|
|
11
|
+
|
|
3
12
|
## [0.9.62] - 2026-07-22
|
|
4
13
|
### Added
|
|
5
14
|
- H8 prep: `BondRegistry.erase_partner!(identity:)` — thread-safe deletion of a single bond entry; marks registry dirty; emits `gaia.bond.erased` event if `Legion::Events` is defined; idempotent (no raise on missing identity)
|
|
@@ -11,9 +11,10 @@ module Legion
|
|
|
11
11
|
|
|
12
12
|
TO_APOLLO_TAGS = %w[self-knowledge bond].freeze
|
|
13
13
|
|
|
14
|
-
@bonds
|
|
15
|
-
@
|
|
16
|
-
@
|
|
14
|
+
@bonds = Concurrent::Hash.new
|
|
15
|
+
@bond_states = Concurrent::Hash.new
|
|
16
|
+
@dirty = false
|
|
17
|
+
@mutex = Mutex.new
|
|
17
18
|
|
|
18
19
|
module_function
|
|
19
20
|
|
|
@@ -262,9 +263,26 @@ module Legion
|
|
|
262
263
|
{ erased: !erased.nil?, identity: identity }
|
|
263
264
|
end
|
|
264
265
|
|
|
266
|
+
def set_bond_state(identity, state)
|
|
267
|
+
@bond_states[identity.to_s] = state.to_sym
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def bond_state(identity)
|
|
271
|
+
@bond_states[identity.to_s]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def terminating?(identity)
|
|
275
|
+
@bond_states[identity.to_s] == :terminating
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def terminated?(identity)
|
|
279
|
+
@bond_states[identity.to_s] == :terminated
|
|
280
|
+
end
|
|
281
|
+
|
|
265
282
|
def reset!
|
|
266
283
|
@mutex.synchronize do
|
|
267
284
|
@bonds = Concurrent::Hash.new
|
|
285
|
+
@bond_states = Concurrent::Hash.new
|
|
268
286
|
@dirty = false
|
|
269
287
|
end
|
|
270
288
|
log.debug('BondRegistry reset')
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/logging/helper'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Gaia
|
|
7
|
+
module DeathProtocol
|
|
8
|
+
extend Legion::Logging::Helper
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# The single entry point for bond termination.
|
|
13
|
+
# confirm: must be true (explicit consent gate).
|
|
14
|
+
# Returns: { terminated: true, identity:, receipt: { store => result } }
|
|
15
|
+
def terminate_bond(identity:, confirm:)
|
|
16
|
+
raise ArgumentError, 'confirm: must be true to terminate' unless confirm == true
|
|
17
|
+
|
|
18
|
+
identity_str = identity.to_s
|
|
19
|
+
|
|
20
|
+
# 1. Set bond state to :terminating (blocks new writes)
|
|
21
|
+
Legion::Gaia::BondRegistry.set_bond_state(identity_str, :terminating)
|
|
22
|
+
|
|
23
|
+
# 2. Walk every store's erase_partner!
|
|
24
|
+
receipt = build_erasure_receipt(identity_str)
|
|
25
|
+
|
|
26
|
+
# 3. Set bond state to :terminated
|
|
27
|
+
Legion::Gaia::BondRegistry.set_bond_state(identity_str, :terminated)
|
|
28
|
+
|
|
29
|
+
# 4. Emit event
|
|
30
|
+
if defined?(Legion::Events) && Legion::Events.respond_to?(:emit)
|
|
31
|
+
Legion::Events.emit('gaia.bond.terminated', identity: identity_str, receipt: receipt)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 5. Log receipt
|
|
35
|
+
log.info("[gaia] bond terminated identity=#{identity_str} stores=#{receipt.keys.size}")
|
|
36
|
+
|
|
37
|
+
{ terminated: true, identity: identity_str, receipt: receipt }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def build_erasure_receipt(identity_str)
|
|
43
|
+
{
|
|
44
|
+
bond: erase_store(:bond) { Legion::Gaia::BondRegistry.erase_partner!(identity: identity_str) },
|
|
45
|
+
behavioral_synapses: erase_store(:behavioral_synapses) do
|
|
46
|
+
Legion::Gaia::BehavioralSynapse.erase_partner!(identity: identity_str)
|
|
47
|
+
end,
|
|
48
|
+
session: erase_store(:session) do
|
|
49
|
+
Legion::Gaia.session_store&.erase_partner!(identity: identity_str)
|
|
50
|
+
end,
|
|
51
|
+
audit: erase_store(:audit) do
|
|
52
|
+
Legion::Gaia::AuditObserver.instance.erase_partner!(identity: identity_str)
|
|
53
|
+
end,
|
|
54
|
+
attribution: erase_store(:attribution) { Legion::Gaia.erase_attribution!(identity: identity_str) },
|
|
55
|
+
calibration: erase_calibration(identity_str),
|
|
56
|
+
preferences: erase_preferences(identity_str),
|
|
57
|
+
memory_traces: erase_memory_traces(identity_str),
|
|
58
|
+
predictions: erase_predictions(identity_str),
|
|
59
|
+
trust: erase_trust(identity_str)
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def erase_store(name)
|
|
64
|
+
result = yield
|
|
65
|
+
result.is_a?(Hash) ? result : { erased: true }
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
log.error("[gaia] death_protocol store=#{name} error=#{e.message}")
|
|
68
|
+
raise
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def erase_calibration(identity_str)
|
|
72
|
+
unless defined?(Legion::Extensions::Agentic::Social::Calibration::Runners::Calibration)
|
|
73
|
+
return { skipped: true, reason: :calibration_unavailable }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
runner = Object.new
|
|
77
|
+
runner.extend(Legion::Extensions::Agentic::Social::Calibration::Runners::Calibration)
|
|
78
|
+
return { skipped: true, reason: :no_erase_method } unless runner.respond_to?(:erase_partner!)
|
|
79
|
+
|
|
80
|
+
erase_store(:calibration) { runner.erase_partner!(identity: identity_str) }
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
log.error("[gaia] death_protocol store=calibration error=#{e.message}")
|
|
83
|
+
raise
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def erase_preferences(identity_str)
|
|
87
|
+
unless defined?(Legion::Extensions::Mesh::Helpers::PreferenceProfile)
|
|
88
|
+
return { skipped: true, reason: :preferences_unavailable }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
klass = Legion::Extensions::Mesh::Helpers::PreferenceProfile
|
|
92
|
+
return { skipped: true, reason: :no_erase_method } unless klass.respond_to?(:erase_partner!)
|
|
93
|
+
|
|
94
|
+
erase_store(:preferences) { klass.erase_partner!(identity: identity_str) }
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
log.error("[gaia] death_protocol store=preferences error=#{e.message}")
|
|
97
|
+
raise
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def erase_memory_traces(identity_str)
|
|
101
|
+
unless defined?(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
102
|
+
return { skipped: true, reason: :memory_trace_unavailable }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
runner = Object.new
|
|
106
|
+
runner.extend(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
107
|
+
return { skipped: true, reason: :no_erase_method } unless runner.respond_to?(:erase_partner!)
|
|
108
|
+
|
|
109
|
+
erase_store(:memory_traces) { runner.erase_partner!(identity: identity_str) }
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
log.error("[gaia] death_protocol store=memory_traces error=#{e.message}")
|
|
112
|
+
raise
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def erase_predictions(identity_str)
|
|
116
|
+
unless defined?(Legion::Extensions::Agentic::Prediction::Helpers::Store)
|
|
117
|
+
return { skipped: true, reason: :predictions_unavailable }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
store = Legion::Extensions::Agentic::Prediction::Helpers::Store
|
|
121
|
+
return { skipped: true, reason: :no_erase_method } unless store.respond_to?(:erase_partner!)
|
|
122
|
+
|
|
123
|
+
erase_store(:predictions) { store.erase_partner!(identity: identity_str) }
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
log.error("[gaia] death_protocol store=predictions error=#{e.message}")
|
|
126
|
+
raise
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def erase_trust(identity_str)
|
|
130
|
+
unless defined?(Legion::Extensions::Agentic::Social::Trust::Helpers::TrustStore)
|
|
131
|
+
return { skipped: true, reason: :trust_unavailable }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
store = Legion::Extensions::Agentic::Social::Trust::Helpers::TrustStore
|
|
135
|
+
return { skipped: true, reason: :no_erase_method } unless store.respond_to?(:erase_partner!)
|
|
136
|
+
|
|
137
|
+
erase_store(:trust) { store.erase_partner!(identity: identity_str) }
|
|
138
|
+
rescue StandardError => e
|
|
139
|
+
log.error("[gaia] death_protocol store=trust error=#{e.message}")
|
|
140
|
+
raise
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
module_function :build_erasure_receipt, :erase_store, :erase_calibration, :erase_preferences,
|
|
144
|
+
:erase_memory_traces, :erase_predictions, :erase_trust
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
data/lib/legion/gaia/routes.rb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
module Legion
|
|
11
11
|
module Gaia
|
|
12
12
|
module Routes
|
|
13
|
-
def self.registered(app)
|
|
13
|
+
def self.registered(app) # rubocop:disable Metrics/AbcSize
|
|
14
14
|
app.helpers do # rubocop:disable Metrics/BlockLength
|
|
15
15
|
unless method_defined?(:gaia_available?)
|
|
16
16
|
define_method(:gaia_available?) do
|
|
@@ -62,6 +62,7 @@ module Legion
|
|
|
62
62
|
register_sessions_route(app)
|
|
63
63
|
register_teams_webhook_route(app)
|
|
64
64
|
register_ingest_route(app)
|
|
65
|
+
register_bond_terminate_route(app)
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
def self.register_status_route(app)
|
|
@@ -193,10 +194,20 @@ module Legion
|
|
|
193
194
|
end
|
|
194
195
|
end
|
|
195
196
|
|
|
197
|
+
def self.register_bond_terminate_route(app)
|
|
198
|
+
app.delete '/api/gaia/bond/:identity' do
|
|
199
|
+
identity = params[:identity]
|
|
200
|
+
halt 400, json_error('missing_identity', 'identity is required') if identity.to_s.empty?
|
|
201
|
+
|
|
202
|
+
result = Legion::Gaia::DeathProtocol.terminate_bond(identity: identity, confirm: true)
|
|
203
|
+
json_response(result)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
196
207
|
class << self
|
|
197
208
|
private :register_status_route, :register_ticks_route, :register_channels_route,
|
|
198
209
|
:register_buffer_route, :register_sessions_route, :register_teams_webhook_route,
|
|
199
|
-
:register_ingest_route
|
|
210
|
+
:register_ingest_route, :register_bond_terminate_route
|
|
200
211
|
end
|
|
201
212
|
end
|
|
202
213
|
end
|
data/lib/legion/gaia/version.rb
CHANGED
data/lib/legion/gaia.rb
CHANGED
|
@@ -32,6 +32,7 @@ require 'legion/gaia/proactive'
|
|
|
32
32
|
require 'legion/gaia/offline_handler'
|
|
33
33
|
require 'legion/gaia/proactive_dispatcher'
|
|
34
34
|
require 'legion/gaia/bond_registry'
|
|
35
|
+
require 'legion/gaia/death_protocol'
|
|
35
36
|
require 'legion/gaia/behavioral_synapse'
|
|
36
37
|
require 'legion/gaia/partner_model'
|
|
37
38
|
require 'legion/gaia/tracker_persistence'
|
|
@@ -206,10 +207,15 @@ module Legion
|
|
|
206
207
|
def ingest(input_frame)
|
|
207
208
|
return { ingested: false, reason: :not_started } unless started?
|
|
208
209
|
|
|
210
|
+
identity = extract_identity(input_frame)
|
|
211
|
+
if identity && (BondRegistry.terminating?(identity.to_s) || BondRegistry.terminated?(identity.to_s))
|
|
212
|
+
log.warn("[gaia] rejected ingest for terminated/terminating identity=#{identity}")
|
|
213
|
+
return { ingested: false, reason: :identity_terminated }
|
|
214
|
+
end
|
|
215
|
+
|
|
209
216
|
signal = input_frame.to_signal
|
|
210
217
|
@sensory_buffer.push(signal)
|
|
211
218
|
|
|
212
|
-
identity = extract_identity(input_frame)
|
|
213
219
|
session = @session_store&.find_or_create(identity: identity || :anonymous)
|
|
214
220
|
@session_store&.touch(session.id, channel_id: input_frame.channel_id) if session
|
|
215
221
|
|
|
@@ -290,6 +296,12 @@ module Legion
|
|
|
290
296
|
def record_response_applied(advisory_id:, identity:, applied:)
|
|
291
297
|
return unless started?
|
|
292
298
|
|
|
299
|
+
identity_str = identity.to_s
|
|
300
|
+
if BondRegistry.terminating?(identity_str) || BondRegistry.terminated?(identity_str)
|
|
301
|
+
log.warn("[gaia] rejected write for terminated/terminating identity=#{identity_str}")
|
|
302
|
+
return { recorded: false, reason: :identity_terminated }
|
|
303
|
+
end
|
|
304
|
+
|
|
293
305
|
synapse_count = Array(applied[:behavioral_synapse_ids]).size
|
|
294
306
|
log.info("[gaia] attribution advisory_id=#{advisory_id} identity=#{identity} synapses=#{synapse_count}")
|
|
295
307
|
|
|
@@ -563,8 +575,17 @@ module Legion
|
|
|
563
575
|
ctx[:channel_identity] || ctx[:user_id] || ctx[:identity]
|
|
564
576
|
end
|
|
565
577
|
|
|
578
|
+
def identity_lifecycle_blocked?(identity_str)
|
|
579
|
+
return false unless BondRegistry.terminating?(identity_str) || BondRegistry.terminated?(identity_str)
|
|
580
|
+
|
|
581
|
+
log.warn("[gaia] rejected write for terminated/terminating identity=#{identity_str}")
|
|
582
|
+
true
|
|
583
|
+
end
|
|
584
|
+
|
|
566
585
|
def observe_interlocutor(input_frame, identity)
|
|
567
586
|
identity_str = identity.to_s
|
|
587
|
+
return if identity_lifecycle_blocked?(identity_str)
|
|
588
|
+
|
|
568
589
|
auth_ctx = input_frame.auth_context || {}
|
|
569
590
|
|
|
570
591
|
# Ensure the identity is registered so reinforce has a target
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-gaia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.63
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -432,6 +432,7 @@ files:
|
|
|
432
432
|
- lib/legion/gaia/channels/teams/webhook_handler.rb
|
|
433
433
|
- lib/legion/gaia/channels/teams_adapter.rb
|
|
434
434
|
- lib/legion/gaia/cognitive_bus.rb
|
|
435
|
+
- lib/legion/gaia/death_protocol.rb
|
|
435
436
|
- lib/legion/gaia/input_frame.rb
|
|
436
437
|
- lib/legion/gaia/intent_classifier.rb
|
|
437
438
|
- lib/legion/gaia/logging.rb
|