lex-agentic-self 0.1.0
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-self.gemspec +31 -0
- data/lib/legion/extensions/agentic/self/agency/client.rb +21 -0
- data/lib/legion/extensions/agentic/self/agency/helpers/constants.rb +77 -0
- data/lib/legion/extensions/agentic/self/agency/helpers/efficacy_model.rb +136 -0
- data/lib/legion/extensions/agentic/self/agency/helpers/outcome_event.rb +52 -0
- data/lib/legion/extensions/agentic/self/agency/runners/agency.rb +117 -0
- data/lib/legion/extensions/agentic/self/agency/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/agency.rb +19 -0
- data/lib/legion/extensions/agentic/self/anchor/client.rb +15 -0
- data/lib/legion/extensions/agentic/self/anchor/helpers/anchor.rb +92 -0
- data/lib/legion/extensions/agentic/self/anchor/helpers/anchor_engine.rb +123 -0
- data/lib/legion/extensions/agentic/self/anchor/helpers/chain.rb +93 -0
- data/lib/legion/extensions/agentic/self/anchor/helpers/constants.rb +46 -0
- data/lib/legion/extensions/agentic/self/anchor/runners/cognitive_anchor.rb +70 -0
- data/lib/legion/extensions/agentic/self/anchor/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/anchor.rb +22 -0
- data/lib/legion/extensions/agentic/self/anosognosia/client.rb +28 -0
- data/lib/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine.rb +153 -0
- data/lib/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit.rb +71 -0
- data/lib/legion/extensions/agentic/self/anosognosia/helpers/constants.rb +29 -0
- data/lib/legion/extensions/agentic/self/anosognosia/runners/anosognosia.rb +98 -0
- data/lib/legion/extensions/agentic/self/anosognosia/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/anosognosia.rb +19 -0
- data/lib/legion/extensions/agentic/self/architecture/client.rb +19 -0
- data/lib/legion/extensions/agentic/self/architecture/helpers/architecture_engine.rb +167 -0
- data/lib/legion/extensions/agentic/self/architecture/helpers/connection.rb +57 -0
- data/lib/legion/extensions/agentic/self/architecture/helpers/constants.rb +37 -0
- data/lib/legion/extensions/agentic/self/architecture/helpers/subsystem.rb +80 -0
- data/lib/legion/extensions/agentic/self/architecture/runners/cognitive_architecture.rb +125 -0
- data/lib/legion/extensions/agentic/self/architecture/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/architecture.rb +20 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/actors/idle.rb +45 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/client.rb +28 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/helpers/constants.rb +53 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine.rb +221 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought.rb +60 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network.rb +122 -0
- data/lib/legion/extensions/agentic/self/default_mode_network/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/default_mode_network.rb +20 -0
- data/lib/legion/extensions/agentic/self/fingerprint/client.rb +28 -0
- data/lib/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait.rb +73 -0
- data/lib/legion/extensions/agentic/self/fingerprint/helpers/constants.rb +60 -0
- data/lib/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine.rb +169 -0
- data/lib/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint.rb +86 -0
- data/lib/legion/extensions/agentic/self/fingerprint/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/fingerprint.rb +19 -0
- data/lib/legion/extensions/agentic/self/identity/actors/credential_refresh.rb +49 -0
- data/lib/legion/extensions/agentic/self/identity/actors/orphan_check.rb +52 -0
- data/lib/legion/extensions/agentic/self/identity/client.rb +27 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/dimensions.rb +75 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/fingerprint.rb +170 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/graph_client.rb +29 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/graph_token.rb +36 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/token_cache.rb +59 -0
- data/lib/legion/extensions/agentic/self/identity/helpers/vault_secrets.rb +80 -0
- data/lib/legion/extensions/agentic/self/identity/local_migrations/20260316000030_create_fingerprint.rb +20 -0
- data/lib/legion/extensions/agentic/self/identity/runners/entra.rb +402 -0
- data/lib/legion/extensions/agentic/self/identity/runners/identity.rb +90 -0
- data/lib/legion/extensions/agentic/self/identity/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/identity.rb +28 -0
- data/lib/legion/extensions/agentic/self/metacognition/client.rb +27 -0
- data/lib/legion/extensions/agentic/self/metacognition/helpers/constants.rb +377 -0
- data/lib/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge.rb +85 -0
- data/lib/legion/extensions/agentic/self/metacognition/helpers/registry_store.rb +70 -0
- data/lib/legion/extensions/agentic/self/metacognition/helpers/self_model.rb +160 -0
- data/lib/legion/extensions/agentic/self/metacognition/helpers/snapshot_store.rb +82 -0
- data/lib/legion/extensions/agentic/self/metacognition/runners/metacognition.rb +116 -0
- data/lib/legion/extensions/agentic/self/metacognition/runners/registry.rb +180 -0
- data/lib/legion/extensions/agentic/self/metacognition/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/metacognition.rb +22 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/client.rb +25 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker.rb +96 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants.rb +47 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine.rb +141 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment.rb +79 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring.rb +151 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/metacognitive_monitoring.rb +20 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/client.rb +29 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc.rb +137 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine.rb +119 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/helpers/beat_event.rb +59 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/helpers/constants.rb +66 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/runners/narrative.rb +101 -0
- data/lib/legion/extensions/agentic/self/narrative_arc/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/narrative_arc.rb +20 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/actors/narrative_decay.rb +45 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/client.rb +22 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/helpers/chapter.rb +48 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/helpers/constants.rb +62 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/helpers/episode.rb +67 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine.rb +187 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/helpers/theme.rb +50 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity.rb +158 -0
- data/lib/legion/extensions/agentic/self/narrative_identity/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/narrative_identity.rb +21 -0
- data/lib/legion/extensions/agentic/self/narrative_self/client.rb +27 -0
- data/lib/legion/extensions/agentic/self/narrative_self/helpers/autobiography.rb +187 -0
- data/lib/legion/extensions/agentic/self/narrative_self/helpers/constants.rb +42 -0
- data/lib/legion/extensions/agentic/self/narrative_self/helpers/episode.rb +81 -0
- data/lib/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread.rb +65 -0
- data/lib/legion/extensions/agentic/self/narrative_self/runners/narrative_self.rb +86 -0
- data/lib/legion/extensions/agentic/self/narrative_self/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/narrative_self.rb +20 -0
- data/lib/legion/extensions/agentic/self/personality/client.rb +21 -0
- data/lib/legion/extensions/agentic/self/personality/helpers/constants.rb +84 -0
- data/lib/legion/extensions/agentic/self/personality/helpers/personality_store.rb +126 -0
- data/lib/legion/extensions/agentic/self/personality/helpers/trait_model.rb +147 -0
- data/lib/legion/extensions/agentic/self/personality/runners/personality.rb +102 -0
- data/lib/legion/extensions/agentic/self/personality/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/personality.rb +19 -0
- data/lib/legion/extensions/agentic/self/reflection/client.rb +27 -0
- data/lib/legion/extensions/agentic/self/reflection/helpers/constants.rb +66 -0
- data/lib/legion/extensions/agentic/self/reflection/helpers/llm_enhancer.rb +166 -0
- data/lib/legion/extensions/agentic/self/reflection/helpers/monitors.rb +186 -0
- data/lib/legion/extensions/agentic/self/reflection/helpers/reflection.rb +54 -0
- data/lib/legion/extensions/agentic/self/reflection/helpers/reflection_store.rb +99 -0
- data/lib/legion/extensions/agentic/self/reflection/runners/reflection.rb +199 -0
- data/lib/legion/extensions/agentic/self/reflection/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/reflection.rb +21 -0
- data/lib/legion/extensions/agentic/self/self_model/client.rb +19 -0
- data/lib/legion/extensions/agentic/self/self_model/helpers/capability.rb +93 -0
- data/lib/legion/extensions/agentic/self/self_model/helpers/constants.rb +46 -0
- data/lib/legion/extensions/agentic/self/self_model/helpers/knowledge_domain.rb +82 -0
- data/lib/legion/extensions/agentic/self/self_model/helpers/self_model.rb +150 -0
- data/lib/legion/extensions/agentic/self/self_model/runners/self_model.rb +82 -0
- data/lib/legion/extensions/agentic/self/self_model/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/self_model.rb +21 -0
- data/lib/legion/extensions/agentic/self/self_talk/actors/volume_decay.rb +45 -0
- data/lib/legion/extensions/agentic/self/self_talk/client.rb +30 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/constants.rb +63 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue.rb +114 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn.rb +43 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/inner_voice.rb +77 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer.rb +135 -0
- data/lib/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine.rb +160 -0
- data/lib/legion/extensions/agentic/self/self_talk/runners/self_talk.rb +172 -0
- data/lib/legion/extensions/agentic/self/self_talk/version.rb +13 -0
- data/lib/legion/extensions/agentic/self/self_talk.rb +22 -0
- data/lib/legion/extensions/agentic/self/version.rb +11 -0
- data/lib/legion/extensions/agentic/self.rb +33 -0
- data/spec/legion/extensions/agentic/self/agency/client_spec.rb +67 -0
- data/spec/legion/extensions/agentic/self/agency/helpers/constants_spec.rb +73 -0
- data/spec/legion/extensions/agentic/self/agency/helpers/efficacy_model_spec.rb +190 -0
- data/spec/legion/extensions/agentic/self/agency/helpers/outcome_event_spec.rb +85 -0
- data/spec/legion/extensions/agentic/self/agency/runners/agency_spec.rb +132 -0
- data/spec/legion/extensions/agentic/self/anchor/client_spec.rb +30 -0
- data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_engine_spec.rb +109 -0
- data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_spec.rb +124 -0
- data/spec/legion/extensions/agentic/self/anchor/helpers/chain_spec.rb +106 -0
- data/spec/legion/extensions/agentic/self/anchor/helpers/constants_spec.rb +53 -0
- data/spec/legion/extensions/agentic/self/anchor/runners/cognitive_anchor_spec.rb +70 -0
- data/spec/legion/extensions/agentic/self/anosognosia/anosognosia_spec.rb +15 -0
- data/spec/legion/extensions/agentic/self/anosognosia/client_spec.rb +50 -0
- data/spec/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine_spec.rb +266 -0
- data/spec/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit_spec.rb +150 -0
- data/spec/legion/extensions/agentic/self/anosognosia/helpers/constants_spec.rb +58 -0
- data/spec/legion/extensions/agentic/self/anosognosia/runners/anosognosia_spec.rb +225 -0
- data/spec/legion/extensions/agentic/self/architecture/client_spec.rb +51 -0
- data/spec/legion/extensions/agentic/self/architecture/helpers/architecture_engine_spec.rb +321 -0
- data/spec/legion/extensions/agentic/self/architecture/helpers/connection_spec.rb +118 -0
- data/spec/legion/extensions/agentic/self/architecture/helpers/subsystem_spec.rb +189 -0
- data/spec/legion/extensions/agentic/self/architecture/runners/cognitive_architecture_spec.rb +181 -0
- data/spec/legion/extensions/agentic/self/default_mode_network/client_spec.rb +69 -0
- data/spec/legion/extensions/agentic/self/default_mode_network/helpers/constants_spec.rb +76 -0
- data/spec/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine_spec.rb +321 -0
- data/spec/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought_spec.rb +145 -0
- data/spec/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network_spec.rb +269 -0
- data/spec/legion/extensions/agentic/self/fingerprint/client_spec.rb +54 -0
- data/spec/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait_spec.rb +180 -0
- data/spec/legion/extensions/agentic/self/fingerprint/helpers/constants_spec.rb +108 -0
- data/spec/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine_spec.rb +318 -0
- data/spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_spec.rb +232 -0
- data/spec/legion/extensions/agentic/self/identity/actors/orphan_check_spec.rb +104 -0
- data/spec/legion/extensions/agentic/self/identity/client_spec.rb +32 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/dimensions_spec.rb +51 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/fingerprint_spec.rb +66 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/graph_client_spec.rb +19 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/graph_token_spec.rb +31 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/token_cache_spec.rb +50 -0
- data/spec/legion/extensions/agentic/self/identity/local_persistence_spec.rb +329 -0
- data/spec/legion/extensions/agentic/self/identity/runners/entra_spec.rb +655 -0
- data/spec/legion/extensions/agentic/self/identity/runners/identity_spec.rb +61 -0
- data/spec/legion/extensions/agentic/self/metacognition/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/self/metacognition/helpers/constants_spec.rb +31 -0
- data/spec/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge_spec.rb +102 -0
- data/spec/legion/extensions/agentic/self/metacognition/helpers/registry_store_spec.rb +227 -0
- data/spec/legion/extensions/agentic/self/metacognition/helpers/self_model_spec.rb +117 -0
- data/spec/legion/extensions/agentic/self/metacognition/helpers/snapshot_store_spec.rb +128 -0
- data/spec/legion/extensions/agentic/self/metacognition/runners/metacognition_spec.rb +110 -0
- data/spec/legion/extensions/agentic/self/metacognition/runners/registry_spec.rb +281 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/client_spec.rb +59 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker_spec.rb +143 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants_spec.rb +91 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine_spec.rb +198 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment_spec.rb +172 -0
- data/spec/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring_spec.rb +244 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/client_spec.rb +22 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine_spec.rb +183 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_spec.rb +177 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/helpers/beat_event_spec.rb +96 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/helpers/constants_spec.rb +75 -0
- data/spec/legion/extensions/agentic/self/narrative_arc/runners/narrative_spec.rb +142 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/client_spec.rb +69 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/helpers/chapter_spec.rb +85 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/helpers/constants_spec.rb +83 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/helpers/episode_spec.rb +180 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine_spec.rb +307 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/helpers/theme_spec.rb +107 -0
- data/spec/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity_spec.rb +240 -0
- data/spec/legion/extensions/agentic/self/narrative_self/client_spec.rb +67 -0
- data/spec/legion/extensions/agentic/self/narrative_self/helpers/autobiography_spec.rb +155 -0
- data/spec/legion/extensions/agentic/self/narrative_self/helpers/constants_spec.rb +28 -0
- data/spec/legion/extensions/agentic/self/narrative_self/helpers/episode_spec.rb +144 -0
- data/spec/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread_spec.rb +87 -0
- data/spec/legion/extensions/agentic/self/narrative_self/runners/narrative_self_spec.rb +118 -0
- data/spec/legion/extensions/agentic/self/personality/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/self/personality/helpers/constants_spec.rb +41 -0
- data/spec/legion/extensions/agentic/self/personality/helpers/personality_store_spec.rb +66 -0
- data/spec/legion/extensions/agentic/self/personality/helpers/trait_model_spec.rb +148 -0
- data/spec/legion/extensions/agentic/self/personality/runners/personality_spec.rb +67 -0
- data/spec/legion/extensions/agentic/self/reflection/client_spec.rb +24 -0
- data/spec/legion/extensions/agentic/self/reflection/helpers/llm_enhancer_spec.rb +191 -0
- data/spec/legion/extensions/agentic/self/reflection/helpers/monitors_spec.rb +120 -0
- data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_spec.rb +49 -0
- data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_store_spec.rb +93 -0
- data/spec/legion/extensions/agentic/self/reflection/runners/reflection_spec.rb +204 -0
- data/spec/legion/extensions/agentic/self/self_model/client_spec.rb +55 -0
- data/spec/legion/extensions/agentic/self/self_model/helpers/capability_spec.rb +160 -0
- data/spec/legion/extensions/agentic/self/self_model/helpers/knowledge_domain_spec.rb +128 -0
- data/spec/legion/extensions/agentic/self/self_model/helpers/self_model_spec.rb +238 -0
- data/spec/legion/extensions/agentic/self/self_model/runners/self_model_spec.rb +143 -0
- data/spec/legion/extensions/agentic/self/self_talk/actors/volume_decay_spec.rb +46 -0
- data/spec/legion/extensions/agentic/self/self_talk/client_spec.rb +26 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/constants_spec.rb +110 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_spec.rb +191 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn_spec.rb +78 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/inner_voice_spec.rb +172 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer_spec.rb +206 -0
- data/spec/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine_spec.rb +239 -0
- data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_llm_spec.rb +169 -0
- data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_spec.rb +196 -0
- data/spec/spec_helper.rb +46 -0
- metadata +347 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Self
|
|
7
|
+
module Identity
|
|
8
|
+
module Runners
|
|
9
|
+
# Entra ID Application identity integration for Digital Workers.
|
|
10
|
+
#
|
|
11
|
+
# Permission model:
|
|
12
|
+
# - Entra app CREATION is done by the human owner (requires Application.ReadWrite.All
|
|
13
|
+
# which Legion does not have and should not have)
|
|
14
|
+
# - Legion gets Application.Read.All or Directory.Read.All for read operations
|
|
15
|
+
# - OIDC token validation uses the public JWKS endpoint (no special permission)
|
|
16
|
+
# - Write operations (transfer ownership, disable apps) update the Legion DB
|
|
17
|
+
# and emit events; the human completes the Entra side manually
|
|
18
|
+
module Entra
|
|
19
|
+
GRAPH_API_BASE = 'https://graph.microsoft.com/v1.0'
|
|
20
|
+
ENTRA_JWKS_URL_TEMPLATE = 'https://login.microsoftonline.com/%<tenant_id>s/discovery/v2.0/keys'
|
|
21
|
+
ENTRA_ISSUER_TEMPLATE = 'https://login.microsoftonline.com/%<tenant_id>s/v2.0'
|
|
22
|
+
|
|
23
|
+
# Validate a worker's identity by checking its Entra app registration exists
|
|
24
|
+
# and its OIDC token is valid.
|
|
25
|
+
# OIDC validation uses the public JWKS endpoint — no Graph API permission needed.
|
|
26
|
+
def validate_worker_identity(worker_id:, entra_app_id: nil, token: nil, tenant_id: nil, **)
|
|
27
|
+
worker = find_worker(worker_id)
|
|
28
|
+
return { valid: false, error: 'worker not found' } unless worker
|
|
29
|
+
|
|
30
|
+
app_id = entra_app_id || worker[:entra_app_id]
|
|
31
|
+
return { valid: false, error: 'no entra_app_id' } unless app_id
|
|
32
|
+
|
|
33
|
+
# If a token is provided and legion-crypt has JWKS support, validate it
|
|
34
|
+
if token && defined?(Legion::Crypt::JWT) && Legion::Crypt::JWT.respond_to?(:verify_with_jwks)
|
|
35
|
+
tid = tenant_id || resolve_tenant_id
|
|
36
|
+
return { valid: false, error: 'no tenant_id configured' } unless tid
|
|
37
|
+
|
|
38
|
+
jwks_url = format(ENTRA_JWKS_URL_TEMPLATE, tenant_id: tid)
|
|
39
|
+
issuer = format(ENTRA_ISSUER_TEMPLATE, tenant_id: tid)
|
|
40
|
+
|
|
41
|
+
claims = Legion::Crypt::JWT.verify_with_jwks(
|
|
42
|
+
token,
|
|
43
|
+
jwks_url: jwks_url,
|
|
44
|
+
issuers: [issuer],
|
|
45
|
+
audience: app_id
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
Legion::Logging.debug "[identity:entra] token validated: worker=#{worker_id} sub=#{claims[:sub]}"
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
valid: true,
|
|
52
|
+
worker_id: worker_id,
|
|
53
|
+
entra_app_id: app_id,
|
|
54
|
+
owner_msid: worker[:owner_msid],
|
|
55
|
+
lifecycle: worker[:lifecycle_state],
|
|
56
|
+
claims: claims,
|
|
57
|
+
validated_at: Time.now.utc
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# No token provided — return identity info without token validation
|
|
62
|
+
Legion::Logging.debug "[identity:entra] validate (no token): worker=#{worker_id} entra_app=#{app_id}"
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
valid: true,
|
|
66
|
+
worker_id: worker_id,
|
|
67
|
+
entra_app_id: app_id,
|
|
68
|
+
owner_msid: worker[:owner_msid],
|
|
69
|
+
lifecycle: worker[:lifecycle_state],
|
|
70
|
+
validated_at: Time.now.utc
|
|
71
|
+
}
|
|
72
|
+
rescue Legion::Crypt::JWT::ExpiredTokenError => e
|
|
73
|
+
{ valid: false, error: 'token_expired', message: e.message }
|
|
74
|
+
rescue Legion::Crypt::JWT::InvalidTokenError => e
|
|
75
|
+
{ valid: false, error: 'token_invalid', message: e.message }
|
|
76
|
+
rescue Legion::Crypt::JWT::Error => e
|
|
77
|
+
{ valid: false, error: 'token_error', message: e.message }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Sync the worker's owner from Entra app ownership.
|
|
81
|
+
# Requires: Application.Read.All or Directory.Read.All (read-only)
|
|
82
|
+
# Falls back to local record when Graph API credentials unavailable.
|
|
83
|
+
def sync_owner(worker_id:, **)
|
|
84
|
+
worker = find_worker(worker_id)
|
|
85
|
+
return { synced: false, error: 'worker not found' } unless worker
|
|
86
|
+
|
|
87
|
+
entra_object_id = worker[:entra_object_id]
|
|
88
|
+
return { synced: false, worker_id: worker_id, error: 'no entra_object_id', source: :local } unless entra_object_id
|
|
89
|
+
|
|
90
|
+
creds = resolve_graph_credentials
|
|
91
|
+
unless creds
|
|
92
|
+
Legion::Logging.debug "[identity:entra] sync_owner fallback to local: worker=#{worker_id}"
|
|
93
|
+
return { synced: true, worker_id: worker_id, source: :local,
|
|
94
|
+
owner_msid: worker[:owner_msid], synced_at: Time.now.utc }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
token = Helpers::GraphToken.fetch(**creds)
|
|
98
|
+
conn = Helpers::GraphClient.connection(token: token)
|
|
99
|
+
resp = conn.get("applications/#{entra_object_id}/owners")
|
|
100
|
+
|
|
101
|
+
unless resp.success?
|
|
102
|
+
Legion::Logging.warn "[identity:entra] graph owner sync failed: #{resp.status}"
|
|
103
|
+
return { synced: false, worker_id: worker_id, source: :local, owner_msid: worker[:owner_msid] }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
owners = resp.body['value'] || []
|
|
107
|
+
graph_owner_msid = owners.first&.dig('id')
|
|
108
|
+
changed = graph_owner_msid && graph_owner_msid != worker[:owner_msid].to_s
|
|
109
|
+
|
|
110
|
+
if changed && defined?(Legion::Data::Model::DigitalWorker)
|
|
111
|
+
Legion::Data::Model::DigitalWorker.where(worker_id: worker_id).update(owner_msid: graph_owner_msid)
|
|
112
|
+
if defined?(Legion::Events)
|
|
113
|
+
Legion::Events.emit('worker.owner_changed', { worker_id: worker_id, old: worker[:owner_msid],
|
|
114
|
+
new: graph_owner_msid })
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
{ synced: true, source: :graph_api, worker_id: worker_id,
|
|
119
|
+
owner_msid: graph_owner_msid || worker[:owner_msid], changed: !changed.nil?, synced_at: Time.now.utc }
|
|
120
|
+
rescue Helpers::GraphToken::GraphTokenError, Faraday::Error => e
|
|
121
|
+
Legion::Logging.warn "[identity:entra] graph sync error: #{e.message}"
|
|
122
|
+
{ synced: false, worker_id: worker_id, source: :local, error: e.message }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Transfer ownership of a digital worker to a new human.
|
|
126
|
+
# Updates the Legion DB record and emits an audit event.
|
|
127
|
+
# The Entra app ownership change must be done by the human owner
|
|
128
|
+
# (requires Application.ReadWrite.All which Legion intentionally does not have).
|
|
129
|
+
def transfer_ownership(worker_id:, new_owner_msid:, transferred_by:, reason: nil, **)
|
|
130
|
+
worker = find_worker(worker_id)
|
|
131
|
+
return { transferred: false, error: 'worker not found' } unless worker
|
|
132
|
+
|
|
133
|
+
old_owner = worker[:owner_msid]
|
|
134
|
+
return { transferred: false, error: 'same owner' } if old_owner == new_owner_msid
|
|
135
|
+
|
|
136
|
+
# Update local record — this is the Legion side of the transfer
|
|
137
|
+
if defined?(Legion::Data) && defined?(Legion::Data::Model::DigitalWorker)
|
|
138
|
+
dw = Legion::Data::Model::DigitalWorker.first(worker_id: worker_id)
|
|
139
|
+
dw&.update(owner_msid: new_owner_msid, updated_at: Time.now.utc)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Entra app ownership change requires Application.ReadWrite.All.
|
|
143
|
+
# Legion does not have this permission by design — the human owner
|
|
144
|
+
# must update Entra app ownership separately via Azure Portal or CLI.
|
|
145
|
+
|
|
146
|
+
audit = {
|
|
147
|
+
event: :ownership_transferred,
|
|
148
|
+
worker_id: worker_id,
|
|
149
|
+
from_owner: old_owner,
|
|
150
|
+
to_owner: new_owner_msid,
|
|
151
|
+
transferred_by: transferred_by,
|
|
152
|
+
reason: reason,
|
|
153
|
+
entra_action_required: 'update Entra app ownership via Azure Portal or az CLI',
|
|
154
|
+
at: Time.now.utc
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
Legion::Events.emit('worker.ownership_transferred', audit) if defined?(Legion::Events)
|
|
158
|
+
Legion::Logging.info "[identity:entra] ownership transferred (Legion DB): worker=#{worker_id} " \
|
|
159
|
+
"from=#{old_owner} to=#{new_owner_msid} by=#{transferred_by}"
|
|
160
|
+
Legion::Logging.warn '[identity:entra] Entra app ownership must be updated manually (requires Application.ReadWrite.All)'
|
|
161
|
+
|
|
162
|
+
{ transferred: true }.merge(audit)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Scan for orphaned workers: Entra apps that are disabled or owners no longer active.
|
|
166
|
+
# Requires: Application.Read.All or Directory.Read.All (read-only)
|
|
167
|
+
# Orphan REMEDIATION (disabling apps) requires human action since Legion
|
|
168
|
+
# does not have Application.ReadWrite.All.
|
|
169
|
+
# Falls back to local-only scan when Graph API credentials unavailable.
|
|
170
|
+
def check_orphans(**)
|
|
171
|
+
return { orphans: [], checked: 0, source: :unavailable } unless defined?(Legion::Data) && defined?(Legion::Data::Model::DigitalWorker)
|
|
172
|
+
|
|
173
|
+
active_workers = Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').all
|
|
174
|
+
orphans = []
|
|
175
|
+
skipped = 0
|
|
176
|
+
|
|
177
|
+
creds = resolve_graph_credentials
|
|
178
|
+
conn = nil
|
|
179
|
+
if creds
|
|
180
|
+
token = Helpers::GraphToken.fetch(**creds)
|
|
181
|
+
conn = Helpers::GraphClient.connection(token: token)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
active_workers.each do |worker|
|
|
185
|
+
if system_placeholder?(worker.entra_app_id, worker.worker_id)
|
|
186
|
+
skipped += 1
|
|
187
|
+
next
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
next unless conn
|
|
191
|
+
|
|
192
|
+
orphan_reason = check_worker_orphan_status(conn, worker)
|
|
193
|
+
if orphan_reason
|
|
194
|
+
orphans << worker
|
|
195
|
+
auto_pause_orphan(worker, reason: orphan_reason)
|
|
196
|
+
end
|
|
197
|
+
rescue Faraday::Error => e
|
|
198
|
+
Legion::Logging.warn "[identity:entra] graph error scanning #{worker.worker_id}: #{e.message}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
source = conn ? :graph_api : :local
|
|
202
|
+
Legion::Logging.debug "[identity:entra] orphan check (#{source}): scanned #{active_workers.size}, skipped #{skipped}"
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
orphans: orphans.map { |w| { worker_id: w.worker_id, owner_msid: w.owner_msid, reason: :entra_orphan } },
|
|
206
|
+
checked: active_workers.size - skipped,
|
|
207
|
+
skipped: skipped,
|
|
208
|
+
source: source,
|
|
209
|
+
checked_at: Time.now.utc
|
|
210
|
+
}
|
|
211
|
+
rescue Helpers::GraphToken::GraphTokenError => e
|
|
212
|
+
Legion::Logging.warn "[identity:entra] orphan check token error: #{e.message}"
|
|
213
|
+
{ orphans: [], checked: 0, source: :local, error: e.message, checked_at: Time.now.utc }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Map Entra security group OIDs to Legion governance roles
|
|
217
|
+
def resolve_governance_roles(groups:, **)
|
|
218
|
+
group_map = Legion::Settings.dig(:rbac, :entra, :group_map) || {}
|
|
219
|
+
default_role = Legion::Settings.dig(:rbac, :entra, :default_role) || 'governance-observer'
|
|
220
|
+
matched = Array(groups).filter_map { |oid| group_map[oid] }.uniq
|
|
221
|
+
matched = [default_role] if matched.empty?
|
|
222
|
+
{ success: true, groups: groups, roles: matched }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def refresh_access_token(worker_id:, force: false, **)
|
|
226
|
+
require_relative '../helpers/token_cache'
|
|
227
|
+
|
|
228
|
+
unless force
|
|
229
|
+
cached = Helpers::TokenCache.fetch(worker_id: worker_id)
|
|
230
|
+
if cached && !Helpers::TokenCache.approaching_expiry?(worker_id: worker_id)
|
|
231
|
+
return { refreshed: false, worker_id: worker_id, source: :cache, expires_at: cached[:expires_at] }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
secret = Helpers::VaultSecrets.read_client_secret(worker_id: worker_id)
|
|
236
|
+
return { refreshed: false, worker_id: worker_id, error: 'vault_unavailable' } unless secret
|
|
237
|
+
|
|
238
|
+
tenant_id = resolve_tenant_id
|
|
239
|
+
return { refreshed: false, worker_id: worker_id, error: 'no_tenant_id' } unless tenant_id
|
|
240
|
+
|
|
241
|
+
scope = Legion::Settings.dig(:identity, :entra, :token_scope) || 'https://graph.microsoft.com/.default'
|
|
242
|
+
url = "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token"
|
|
243
|
+
|
|
244
|
+
require 'faraday'
|
|
245
|
+
resp = Faraday.post(url, {
|
|
246
|
+
grant_type: 'client_credentials',
|
|
247
|
+
client_id: secret[:client_id] || secret[:entra_app_id],
|
|
248
|
+
client_secret: secret[:client_secret],
|
|
249
|
+
scope: scope
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
unless resp.success?
|
|
253
|
+
Legion::Logging.warn "[identity] token refresh failed for #{worker_id}: #{resp.status}"
|
|
254
|
+
return { refreshed: false, worker_id: worker_id, error: 'token_request_failed' }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
body = Legion::JSON.load(resp.body)
|
|
258
|
+
expires_in = body[:expires_in]&.to_i || 3600
|
|
259
|
+
Helpers::TokenCache.store(worker_id: worker_id, token: body[:access_token], expires_in: expires_in)
|
|
260
|
+
|
|
261
|
+
{ refreshed: true, worker_id: worker_id, expires_at: Time.now + expires_in }
|
|
262
|
+
rescue StandardError => e
|
|
263
|
+
Legion::Logging.warn "[identity] token refresh error: #{e.message}"
|
|
264
|
+
{ refreshed: false, worker_id: worker_id, error: e.message }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def rotate_client_secret(worker_id:, dry_run: false, **)
|
|
268
|
+
rotation_enabled = Legion::Settings.dig(:identity, :entra, :rotation_enabled)
|
|
269
|
+
buffer_days = Legion::Settings.dig(:identity, :entra, :rotation_buffer_days) || 30
|
|
270
|
+
|
|
271
|
+
secret = Helpers::VaultSecrets.read_client_secret(worker_id: worker_id)
|
|
272
|
+
return { rotated: false, worker_id: worker_id, error: 'vault_unavailable' } unless secret
|
|
273
|
+
|
|
274
|
+
expires_at = secret[:client_secret_expires_at]
|
|
275
|
+
return { rotated: false, worker_id: worker_id, action_required: false, reason: 'no_expiry_tracked' } unless expires_at
|
|
276
|
+
|
|
277
|
+
days_remaining = (Time.parse(expires_at.to_s) - Time.now) / 86_400
|
|
278
|
+
unless days_remaining < buffer_days
|
|
279
|
+
return { rotated: false, worker_id: worker_id, action_required: false,
|
|
280
|
+
days_remaining: days_remaining.round(1) }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
unless rotation_enabled
|
|
284
|
+
Legion::Logging.warn "[identity] credential expiring for #{worker_id} in #{days_remaining.round(1)} days"
|
|
285
|
+
if defined?(Legion::Events)
|
|
286
|
+
Legion::Events.emit('worker.credential_expiry_warning', {
|
|
287
|
+
worker_id: worker_id, days_remaining: days_remaining.round(1)
|
|
288
|
+
})
|
|
289
|
+
end
|
|
290
|
+
return { rotated: false, worker_id: worker_id, action_required: true,
|
|
291
|
+
days_remaining: days_remaining.round(1) }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
return { rotated: false, worker_id: worker_id, dry_run: true, would_rotate: true } if dry_run
|
|
295
|
+
|
|
296
|
+
# Graph API rotation would go here when permission is granted
|
|
297
|
+
{ rotated: false, worker_id: worker_id, error: 'graph_api_rotation_not_implemented' }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def credential_refresh_cycle(**)
|
|
301
|
+
return { workers_checked: 0, error: 'data_unavailable' } unless defined?(Legion::Data::Model::DigitalWorker)
|
|
302
|
+
|
|
303
|
+
workers = Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').all
|
|
304
|
+
results = { workers_checked: 0, refreshed: 0, warned: 0 }
|
|
305
|
+
|
|
306
|
+
workers.each do |worker|
|
|
307
|
+
next if system_placeholder?(worker.entra_app_id, worker.worker_id)
|
|
308
|
+
|
|
309
|
+
results[:workers_checked] += 1
|
|
310
|
+
|
|
311
|
+
token_result = refresh_access_token(worker_id: worker.worker_id)
|
|
312
|
+
results[:refreshed] += 1 if token_result[:refreshed]
|
|
313
|
+
|
|
314
|
+
rotation_result = rotate_client_secret(worker_id: worker.worker_id)
|
|
315
|
+
results[:warned] += 1 if rotation_result[:action_required]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
results
|
|
319
|
+
rescue StandardError => e
|
|
320
|
+
Legion::Logging.warn "[identity] credential refresh cycle error: #{e.message}"
|
|
321
|
+
{ workers_checked: 0, error: e.message }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
private
|
|
325
|
+
|
|
326
|
+
def find_worker(worker_id)
|
|
327
|
+
if defined?(Legion::Data) && defined?(Legion::Data::Model::DigitalWorker)
|
|
328
|
+
worker = Legion::Data::Model::DigitalWorker.first(worker_id: worker_id)
|
|
329
|
+
return worker.to_hash if worker
|
|
330
|
+
end
|
|
331
|
+
nil
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def system_placeholder?(entra_app_id, worker_id)
|
|
335
|
+
return true if entra_app_id.nil? || entra_app_id == 'system'
|
|
336
|
+
return true if entra_app_id == worker_id
|
|
337
|
+
return true if entra_app_id.start_with?('lex-')
|
|
338
|
+
|
|
339
|
+
false
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def resolve_tenant_id
|
|
343
|
+
if defined?(Legion::Settings) &&
|
|
344
|
+
Legion::Settings[:identity]&.dig(:entra, :tenant_id)
|
|
345
|
+
return Legion::Settings[:identity][:entra][:tenant_id]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
nil
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def resolve_graph_credentials
|
|
352
|
+
tenant_id = resolve_tenant_id
|
|
353
|
+
return nil unless tenant_id
|
|
354
|
+
|
|
355
|
+
secret = Helpers::VaultSecrets.read_client_secret(worker_id: 'legion/identity')
|
|
356
|
+
return nil unless secret && secret[:client_id] && secret[:client_secret]
|
|
357
|
+
|
|
358
|
+
{ tenant_id: tenant_id, client_id: secret[:client_id], client_secret: secret[:client_secret] }
|
|
359
|
+
rescue StandardError
|
|
360
|
+
nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def check_worker_orphan_status(conn, worker)
|
|
364
|
+
# Check if the Entra app registration still exists
|
|
365
|
+
if worker.respond_to?(:entra_object_id) && worker.entra_object_id
|
|
366
|
+
app_resp = conn.get("applications/#{worker.entra_object_id}")
|
|
367
|
+
return :entra_app_deleted unless app_resp.success?
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Check if the owner account is still active
|
|
371
|
+
if worker.owner_msid
|
|
372
|
+
user_resp = conn.get("users/#{worker.owner_msid}")
|
|
373
|
+
return :owner_deleted unless user_resp.success?
|
|
374
|
+
return :owner_disabled if user_resp.body['accountEnabled'] == false
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
nil
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def auto_pause_orphan(worker, reason:)
|
|
381
|
+
worker.update(lifecycle_state: 'paused', updated_at: Time.now.utc)
|
|
382
|
+
|
|
383
|
+
if defined?(Legion::Events)
|
|
384
|
+
Legion::Events.emit('worker.orphan_detected', {
|
|
385
|
+
worker_id: worker.worker_id,
|
|
386
|
+
owner_msid: worker.owner_msid,
|
|
387
|
+
reason: reason,
|
|
388
|
+
action: :auto_paused,
|
|
389
|
+
remediation: 'disable or reassign Entra app via Azure Portal',
|
|
390
|
+
at: Time.now.utc
|
|
391
|
+
})
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
Legion::Logging.warn "[identity:entra] orphan detected: worker=#{worker.worker_id} reason=#{reason} — auto-paused"
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Self
|
|
7
|
+
module Identity
|
|
8
|
+
module Runners
|
|
9
|
+
module Identity
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def observe_behavior(dimension:, value:, **)
|
|
14
|
+
fingerprint = identity_fingerprint
|
|
15
|
+
fingerprint.observe(dimension, value)
|
|
16
|
+
|
|
17
|
+
Legion::Logging.debug "[identity] observe: dim=#{dimension} val=#{value.round(2)} " \
|
|
18
|
+
"obs=#{fingerprint.observation_count} maturity=#{fingerprint.maturity}"
|
|
19
|
+
{
|
|
20
|
+
dimension: dimension,
|
|
21
|
+
recorded: true,
|
|
22
|
+
observation_count: fingerprint.observation_count,
|
|
23
|
+
maturity: fingerprint.maturity
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def observe_all(observations:, **)
|
|
28
|
+
fingerprint = identity_fingerprint
|
|
29
|
+
fingerprint.observe_all(observations)
|
|
30
|
+
|
|
31
|
+
Legion::Logging.debug "[identity] observe_all: dims=#{observations.keys.join(',')} " \
|
|
32
|
+
"obs=#{fingerprint.observation_count} maturity=#{fingerprint.maturity}"
|
|
33
|
+
{
|
|
34
|
+
dimensions_observed: observations.keys,
|
|
35
|
+
observation_count: fingerprint.observation_count,
|
|
36
|
+
maturity: fingerprint.maturity
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def check_entropy(observations: {}, **)
|
|
41
|
+
fingerprint = identity_fingerprint
|
|
42
|
+
entropy = fingerprint.current_entropy(observations)
|
|
43
|
+
classification = Helpers::Dimensions.classify_entropy(entropy)
|
|
44
|
+
trend = fingerprint.entropy_trend
|
|
45
|
+
|
|
46
|
+
result = {
|
|
47
|
+
entropy: entropy,
|
|
48
|
+
classification: classification,
|
|
49
|
+
trend: trend,
|
|
50
|
+
in_range: Helpers::Dimensions::OPTIMAL_ENTROPY_RANGE.cover?(entropy)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case classification
|
|
54
|
+
when :high_entropy
|
|
55
|
+
result[:warning] = :possible_impersonation_or_drift
|
|
56
|
+
result[:action] = :enter_caution_mode
|
|
57
|
+
Legion::Logging.warn "[identity] high entropy detected: #{entropy.round(3)} trend=#{trend} - possible impersonation"
|
|
58
|
+
when :low_entropy
|
|
59
|
+
result[:warning] = :possible_automation
|
|
60
|
+
result[:action] = :trigger_verification
|
|
61
|
+
Legion::Logging.warn "[identity] low entropy detected: #{entropy.round(3)} trend=#{trend} - possible automation"
|
|
62
|
+
else
|
|
63
|
+
Legion::Logging.debug "[identity] entropy check: #{entropy.round(3)} classification=#{classification} trend=#{trend}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def identity_status(**)
|
|
70
|
+
fingerprint = identity_fingerprint
|
|
71
|
+
Legion::Logging.debug "[identity] status: maturity=#{fingerprint.maturity} observations=#{fingerprint.observation_count}"
|
|
72
|
+
fingerprint.to_h
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def identity_maturity(**)
|
|
76
|
+
{ maturity: identity_fingerprint.maturity }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def identity_fingerprint
|
|
82
|
+
@identity_fingerprint ||= Helpers::Fingerprint.new
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/self/identity/version'
|
|
4
|
+
require 'legion/extensions/agentic/self/identity/helpers/dimensions'
|
|
5
|
+
require 'legion/extensions/agentic/self/identity/helpers/fingerprint'
|
|
6
|
+
require 'legion/extensions/agentic/self/identity/helpers/vault_secrets'
|
|
7
|
+
require 'legion/extensions/agentic/self/identity/runners/identity'
|
|
8
|
+
require 'legion/extensions/agentic/self/identity/runners/entra'
|
|
9
|
+
require 'legion/extensions/agentic/self/identity/actors/orphan_check'
|
|
10
|
+
require 'legion/extensions/agentic/self/identity/client'
|
|
11
|
+
|
|
12
|
+
module Legion
|
|
13
|
+
module Extensions
|
|
14
|
+
module Agentic
|
|
15
|
+
module Self
|
|
16
|
+
module Identity
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if defined?(Legion::Data::Local)
|
|
22
|
+
Legion::Data::Local.register_migrations(
|
|
23
|
+
name: :identity,
|
|
24
|
+
path: File.join(__dir__, 'identity', 'local_migrations')
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/self/metacognition/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/self/metacognition/helpers/self_model'
|
|
5
|
+
require 'legion/extensions/agentic/self/metacognition/helpers/snapshot_store'
|
|
6
|
+
require 'legion/extensions/agentic/self/metacognition/helpers/narrator_bridge'
|
|
7
|
+
require 'legion/extensions/agentic/self/metacognition/runners/metacognition'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Agentic
|
|
12
|
+
module Self
|
|
13
|
+
module Metacognition
|
|
14
|
+
class Client
|
|
15
|
+
include Runners::Metacognition
|
|
16
|
+
|
|
17
|
+
attr_reader :snapshot_store
|
|
18
|
+
|
|
19
|
+
def initialize(snapshot_store: nil, **)
|
|
20
|
+
@snapshot_store = snapshot_store || Helpers::SnapshotStore.new
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|