@smilintux/skcapstone 0.1.0 → 0.2.3
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.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Fortress — integrity sealing, at-rest encryption, and tamper alerts.
|
|
3
|
+
|
|
4
|
+
Every memory gets an HMAC-SHA256 integrity seal on write. On read, the
|
|
5
|
+
seal is verified and a tamper alert fires if it doesn't match. Optional
|
|
6
|
+
at-rest encryption uses the KMS service key derived from the agent's
|
|
7
|
+
master key.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
Fortress wraps the memory engine's _save_entry / _load_entry with:
|
|
11
|
+
1. Auto-seal: HMAC-SHA256 integrity hash on every write.
|
|
12
|
+
2. At-rest encryption: Fernet (AES-128-CBC + HMAC) via KMS service key.
|
|
13
|
+
3. Tamper alerts: integrity verification on every read.
|
|
14
|
+
4. Audit trail: every access, seal, and violation logged.
|
|
15
|
+
|
|
16
|
+
Storage layout:
|
|
17
|
+
~/.skcapstone/memory/
|
|
18
|
+
├── short-term/
|
|
19
|
+
│ └── abc123def456.json # Sealed (and optionally encrypted)
|
|
20
|
+
├── mid-term/
|
|
21
|
+
├── long-term/
|
|
22
|
+
└── fortress.json # Fortress configuration
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import hmac
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any, Optional
|
|
34
|
+
|
|
35
|
+
from pydantic import BaseModel, Field
|
|
36
|
+
|
|
37
|
+
from .models import MemoryEntry
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger("skcapstone.memory_fortress")
|
|
40
|
+
|
|
41
|
+
# Sentinel field name inside the JSON envelope
|
|
42
|
+
_SEAL_FIELD = "__fortress_seal"
|
|
43
|
+
_ENCRYPTED_FIELD = "__fortress_encrypted"
|
|
44
|
+
_SEALED_AT_FIELD = "__fortress_sealed_at"
|
|
45
|
+
_KEY_ID_FIELD = "__fortress_key_id"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Models
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class FortressConfig(BaseModel):
|
|
54
|
+
"""Persistent configuration for the memory fortress."""
|
|
55
|
+
|
|
56
|
+
enabled: bool = True
|
|
57
|
+
encryption_enabled: bool = False
|
|
58
|
+
seal_algorithm: str = "hmac-sha256"
|
|
59
|
+
kms_service_label: str = "memory-fortress"
|
|
60
|
+
audit_events: bool = True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SealResult(BaseModel):
|
|
64
|
+
"""Result of a seal or verify operation."""
|
|
65
|
+
|
|
66
|
+
memory_id: str
|
|
67
|
+
sealed: bool
|
|
68
|
+
verified: Optional[bool] = None
|
|
69
|
+
tampered: bool = False
|
|
70
|
+
error: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# MemoryFortress
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MemoryFortress:
|
|
79
|
+
"""Integrity sealing, encryption, and tamper detection for memories.
|
|
80
|
+
|
|
81
|
+
Wraps the memory engine's file I/O to ensure every memory is sealed
|
|
82
|
+
with an HMAC-SHA256 and optionally encrypted at rest using the KMS.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
home: Agent home directory (~/.skcapstone).
|
|
86
|
+
seal_key: Optional explicit HMAC key (bytes). If not provided,
|
|
87
|
+
derived from the KMS master key.
|
|
88
|
+
encryption_enabled: Whether to encrypt memory content at rest.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
home: Path,
|
|
94
|
+
seal_key: Optional[bytes] = None,
|
|
95
|
+
encryption_enabled: bool = False,
|
|
96
|
+
) -> None:
|
|
97
|
+
self._home = home
|
|
98
|
+
self._seal_key = seal_key
|
|
99
|
+
self._encryption_enabled = encryption_enabled
|
|
100
|
+
self._config: Optional[FortressConfig] = None
|
|
101
|
+
self._kms_key_id: Optional[str] = None
|
|
102
|
+
|
|
103
|
+
def initialize(self) -> FortressConfig:
|
|
104
|
+
"""Initialize the memory fortress.
|
|
105
|
+
|
|
106
|
+
Sets up the seal key (from KMS or explicit), loads or creates
|
|
107
|
+
configuration, and ensures the KMS service key exists if
|
|
108
|
+
encryption is enabled.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
FortressConfig with current settings.
|
|
112
|
+
"""
|
|
113
|
+
config = self._load_config()
|
|
114
|
+
|
|
115
|
+
if self._encryption_enabled:
|
|
116
|
+
config.encryption_enabled = True
|
|
117
|
+
|
|
118
|
+
if self._seal_key is None:
|
|
119
|
+
self._seal_key = self._derive_seal_key()
|
|
120
|
+
|
|
121
|
+
if config.encryption_enabled:
|
|
122
|
+
self._ensure_encryption_key()
|
|
123
|
+
|
|
124
|
+
self._config = config
|
|
125
|
+
self._save_config(config)
|
|
126
|
+
|
|
127
|
+
self._audit("FORTRESS_INIT", "Memory fortress initialized", metadata={
|
|
128
|
+
"encryption_enabled": config.encryption_enabled,
|
|
129
|
+
"seal_algorithm": config.seal_algorithm,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return config
|
|
133
|
+
|
|
134
|
+
def seal_entry(self, entry: MemoryEntry) -> dict[str, Any]:
|
|
135
|
+
"""Seal a memory entry with an integrity HMAC.
|
|
136
|
+
|
|
137
|
+
Computes HMAC-SHA256 over the memory content and metadata,
|
|
138
|
+
then embeds the seal in the serialized data. If encryption
|
|
139
|
+
is enabled, the content field is encrypted before sealing.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
entry: The MemoryEntry to seal.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict ready to be written as JSON, with fortress fields.
|
|
146
|
+
"""
|
|
147
|
+
data = json.loads(entry.model_dump_json())
|
|
148
|
+
config = self._get_config()
|
|
149
|
+
|
|
150
|
+
if config.encryption_enabled:
|
|
151
|
+
data = self._encrypt_content(data)
|
|
152
|
+
|
|
153
|
+
seal = self._compute_seal(data)
|
|
154
|
+
data[_SEAL_FIELD] = seal
|
|
155
|
+
data[_SEALED_AT_FIELD] = datetime.now(timezone.utc).isoformat()
|
|
156
|
+
|
|
157
|
+
if self._kms_key_id:
|
|
158
|
+
data[_KEY_ID_FIELD] = self._kms_key_id
|
|
159
|
+
|
|
160
|
+
if config.audit_events:
|
|
161
|
+
self._audit("MEMORY_SEALED", f"Memory {entry.memory_id} sealed", metadata={
|
|
162
|
+
"memory_id": entry.memory_id,
|
|
163
|
+
"layer": entry.layer.value,
|
|
164
|
+
"encrypted": config.encryption_enabled,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return data
|
|
168
|
+
|
|
169
|
+
def verify_and_load(self, path: Path) -> tuple[Optional[MemoryEntry], SealResult]:
|
|
170
|
+
"""Load a memory file, verify its integrity seal, and decrypt.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
path: Path to the memory JSON file.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Tuple of (MemoryEntry or None, SealResult).
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
raw = path.read_text(encoding="utf-8")
|
|
180
|
+
data = json.loads(raw)
|
|
181
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
182
|
+
return None, SealResult(
|
|
183
|
+
memory_id=path.stem,
|
|
184
|
+
sealed=False,
|
|
185
|
+
error=f"Cannot read: {exc}",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
memory_id = data.get("memory_id", path.stem)
|
|
189
|
+
|
|
190
|
+
stored_seal = data.pop(_SEAL_FIELD, None)
|
|
191
|
+
data.pop(_SEALED_AT_FIELD, None)
|
|
192
|
+
data.pop(_KEY_ID_FIELD, None)
|
|
193
|
+
|
|
194
|
+
if stored_seal is None:
|
|
195
|
+
# Legacy unsealed memory — load without verification
|
|
196
|
+
try:
|
|
197
|
+
entry = MemoryEntry(**data)
|
|
198
|
+
return entry, SealResult(
|
|
199
|
+
memory_id=memory_id,
|
|
200
|
+
sealed=False,
|
|
201
|
+
verified=None,
|
|
202
|
+
)
|
|
203
|
+
except Exception as exc:
|
|
204
|
+
return None, SealResult(
|
|
205
|
+
memory_id=memory_id,
|
|
206
|
+
sealed=False,
|
|
207
|
+
error=str(exc),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Verify integrity
|
|
211
|
+
expected_seal = self._compute_seal(data)
|
|
212
|
+
if not hmac.compare_digest(stored_seal, expected_seal):
|
|
213
|
+
self._audit("MEMORY_TAMPER_ALERT", f"TAMPERED: Memory {memory_id} failed integrity check", metadata={
|
|
214
|
+
"memory_id": memory_id,
|
|
215
|
+
"expected_seal": expected_seal[:16] + "...",
|
|
216
|
+
"actual_seal": stored_seal[:16] + "...",
|
|
217
|
+
"path": str(path),
|
|
218
|
+
})
|
|
219
|
+
return None, SealResult(
|
|
220
|
+
memory_id=memory_id,
|
|
221
|
+
sealed=True,
|
|
222
|
+
verified=False,
|
|
223
|
+
tampered=True,
|
|
224
|
+
error="Integrity seal mismatch — possible tampering",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Decrypt if encrypted
|
|
228
|
+
is_encrypted = data.pop(_ENCRYPTED_FIELD, False)
|
|
229
|
+
if is_encrypted:
|
|
230
|
+
data = self._decrypt_content(data)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
entry = MemoryEntry(**data)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
return None, SealResult(
|
|
236
|
+
memory_id=memory_id,
|
|
237
|
+
sealed=True,
|
|
238
|
+
verified=True,
|
|
239
|
+
error=f"Parse error after verification: {exc}",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
config = self._get_config()
|
|
243
|
+
if config.audit_events:
|
|
244
|
+
self._audit("MEMORY_VERIFIED", f"Memory {memory_id} integrity verified", metadata={
|
|
245
|
+
"memory_id": memory_id,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
return entry, SealResult(
|
|
249
|
+
memory_id=memory_id,
|
|
250
|
+
sealed=True,
|
|
251
|
+
verified=True,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def save_sealed(self, home: Path, entry: MemoryEntry) -> Path:
|
|
255
|
+
"""Seal and save a memory entry to disk.
|
|
256
|
+
|
|
257
|
+
Atomic write using tmp + rename pattern.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
home: Agent home directory.
|
|
261
|
+
entry: The MemoryEntry to seal and save.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Path where the entry was written.
|
|
265
|
+
"""
|
|
266
|
+
from .memory_engine import _entry_path
|
|
267
|
+
|
|
268
|
+
sealed_data = self.seal_entry(entry)
|
|
269
|
+
path = _entry_path(home, entry)
|
|
270
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
271
|
+
|
|
272
|
+
tmp_path = path.with_suffix(".json.tmp")
|
|
273
|
+
tmp_path.write_text(
|
|
274
|
+
json.dumps(sealed_data, indent=2, default=str),
|
|
275
|
+
encoding="utf-8",
|
|
276
|
+
)
|
|
277
|
+
tmp_path.rename(path)
|
|
278
|
+
|
|
279
|
+
return path
|
|
280
|
+
|
|
281
|
+
def verify_all(self, home: Path) -> list[SealResult]:
|
|
282
|
+
"""Verify integrity of all memories across all layers.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
home: Agent home directory.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of SealResult for every memory file.
|
|
289
|
+
"""
|
|
290
|
+
from .models import MemoryLayer
|
|
291
|
+
|
|
292
|
+
results: list[SealResult] = []
|
|
293
|
+
mem_dir = home / "memory"
|
|
294
|
+
if not mem_dir.is_dir():
|
|
295
|
+
return results
|
|
296
|
+
|
|
297
|
+
tampered_count = 0
|
|
298
|
+
verified_count = 0
|
|
299
|
+
unsealed_count = 0
|
|
300
|
+
|
|
301
|
+
for layer in MemoryLayer:
|
|
302
|
+
layer_dir = mem_dir / layer.value
|
|
303
|
+
if not layer_dir.is_dir():
|
|
304
|
+
continue
|
|
305
|
+
for f in sorted(layer_dir.glob("*.json")):
|
|
306
|
+
_, result = self.verify_and_load(f)
|
|
307
|
+
results.append(result)
|
|
308
|
+
if result.tampered:
|
|
309
|
+
tampered_count += 1
|
|
310
|
+
elif result.verified:
|
|
311
|
+
verified_count += 1
|
|
312
|
+
elif not result.sealed:
|
|
313
|
+
unsealed_count += 1
|
|
314
|
+
|
|
315
|
+
self._audit("FORTRESS_SCAN", "Full memory integrity scan completed", metadata={
|
|
316
|
+
"total": len(results),
|
|
317
|
+
"verified": verified_count,
|
|
318
|
+
"tampered": tampered_count,
|
|
319
|
+
"unsealed": unsealed_count,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
return results
|
|
323
|
+
|
|
324
|
+
def seal_existing(self, home: Path) -> int:
|
|
325
|
+
"""Seal all existing unsealed memories (migration).
|
|
326
|
+
|
|
327
|
+
Reads each memory file, adds an integrity seal, and
|
|
328
|
+
optionally encrypts if encryption is enabled.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
home: Agent home directory.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Number of memories sealed.
|
|
335
|
+
"""
|
|
336
|
+
from .models import MemoryLayer
|
|
337
|
+
|
|
338
|
+
sealed = 0
|
|
339
|
+
mem_dir = home / "memory"
|
|
340
|
+
if not mem_dir.is_dir():
|
|
341
|
+
return sealed
|
|
342
|
+
|
|
343
|
+
for layer in MemoryLayer:
|
|
344
|
+
layer_dir = mem_dir / layer.value
|
|
345
|
+
if not layer_dir.is_dir():
|
|
346
|
+
continue
|
|
347
|
+
for f in sorted(layer_dir.glob("*.json")):
|
|
348
|
+
try:
|
|
349
|
+
data = json.loads(f.read_text(encoding="utf-8"))
|
|
350
|
+
except (OSError, json.JSONDecodeError):
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
if _SEAL_FIELD in data:
|
|
354
|
+
continue # Already sealed
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
entry = MemoryEntry(**data)
|
|
358
|
+
self.save_sealed(home, entry)
|
|
359
|
+
sealed += 1
|
|
360
|
+
except Exception as exc:
|
|
361
|
+
logger.warning("Cannot seal %s: %s", f.name, exc)
|
|
362
|
+
|
|
363
|
+
if sealed:
|
|
364
|
+
self._audit("FORTRESS_MIGRATION", f"Sealed {sealed} existing memories", metadata={
|
|
365
|
+
"sealed_count": sealed,
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
return sealed
|
|
369
|
+
|
|
370
|
+
def status(self) -> dict[str, Any]:
|
|
371
|
+
"""Return fortress status summary."""
|
|
372
|
+
config = self._get_config()
|
|
373
|
+
return {
|
|
374
|
+
"enabled": config.enabled,
|
|
375
|
+
"encryption_enabled": config.encryption_enabled,
|
|
376
|
+
"seal_algorithm": config.seal_algorithm,
|
|
377
|
+
"kms_service_label": config.kms_service_label,
|
|
378
|
+
"has_seal_key": self._seal_key is not None,
|
|
379
|
+
"has_encryption_key": self._kms_key_id is not None,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# -------------------------------------------------------------------
|
|
383
|
+
# Internal helpers
|
|
384
|
+
# -------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
def _compute_seal(self, data: dict[str, Any]) -> str:
|
|
387
|
+
"""Compute HMAC-SHA256 over serialized memory data.
|
|
388
|
+
|
|
389
|
+
The seal covers the entire JSON payload (excluding the seal
|
|
390
|
+
field itself) to detect any modification.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
data: Memory data dict (without the seal field).
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Hex-encoded HMAC-SHA256 digest.
|
|
397
|
+
"""
|
|
398
|
+
key = self._get_seal_key()
|
|
399
|
+
# Canonical JSON serialization for deterministic hashing
|
|
400
|
+
canonical = json.dumps(data, sort_keys=True, separators=(",", ":"), default=str)
|
|
401
|
+
return hmac.new(key, canonical.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
402
|
+
|
|
403
|
+
def _get_seal_key(self) -> bytes:
|
|
404
|
+
"""Get the HMAC seal key, deriving from KMS if needed."""
|
|
405
|
+
if self._seal_key is not None:
|
|
406
|
+
return self._seal_key
|
|
407
|
+
|
|
408
|
+
self._seal_key = self._derive_seal_key()
|
|
409
|
+
return self._seal_key
|
|
410
|
+
|
|
411
|
+
def _derive_seal_key(self) -> bytes:
|
|
412
|
+
"""Derive a seal key from the KMS master."""
|
|
413
|
+
try:
|
|
414
|
+
from .kms import KeyStore
|
|
415
|
+
|
|
416
|
+
store = KeyStore(self._home)
|
|
417
|
+
store.initialize()
|
|
418
|
+
key_record = store.derive_service_key("memory-fortress-seal")
|
|
419
|
+
material = store.get_key_material(key_record.key_id)
|
|
420
|
+
self._kms_key_id = key_record.key_id
|
|
421
|
+
return material
|
|
422
|
+
except Exception as exc:
|
|
423
|
+
logger.warning("KMS unavailable, using fallback seal key: %s", exc)
|
|
424
|
+
# Fallback: derive from agent identity fingerprint
|
|
425
|
+
return self._fallback_key()
|
|
426
|
+
|
|
427
|
+
def _fallback_key(self) -> bytes:
|
|
428
|
+
"""Derive a seal key from agent identity when KMS is unavailable."""
|
|
429
|
+
identity_path = self._home / "identity" / "identity.json"
|
|
430
|
+
if identity_path.exists():
|
|
431
|
+
try:
|
|
432
|
+
data = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
433
|
+
fp = data.get("fingerprint", "skcapstone-default")
|
|
434
|
+
return hashlib.sha256(fp.encode()).digest()
|
|
435
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
436
|
+
logger.debug("Cannot read identity for fallback key: %s", exc)
|
|
437
|
+
return hashlib.sha256(b"skcapstone-memory-fortress-default").digest()
|
|
438
|
+
|
|
439
|
+
def _ensure_encryption_key(self) -> None:
|
|
440
|
+
"""Ensure a KMS service key exists for memory encryption."""
|
|
441
|
+
try:
|
|
442
|
+
from .kms import KeyStore
|
|
443
|
+
|
|
444
|
+
store = KeyStore(self._home)
|
|
445
|
+
store.initialize()
|
|
446
|
+
key_record = store.derive_service_key("memory-fortress-enc")
|
|
447
|
+
self._kms_key_id = key_record.key_id
|
|
448
|
+
except Exception as exc:
|
|
449
|
+
logger.warning("Cannot create encryption key: %s", exc)
|
|
450
|
+
|
|
451
|
+
def _encrypt_content(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
452
|
+
"""Encrypt the content field using KMS Fernet key."""
|
|
453
|
+
try:
|
|
454
|
+
from .kms import KeyStore, _fernet_encrypt
|
|
455
|
+
|
|
456
|
+
store = KeyStore(self._home)
|
|
457
|
+
store.initialize()
|
|
458
|
+
key_record = store.derive_service_key("memory-fortress-enc")
|
|
459
|
+
key_material = store.get_key_material(key_record.key_id)
|
|
460
|
+
|
|
461
|
+
content = data.get("content", "")
|
|
462
|
+
encrypted = _fernet_encrypt(content.encode("utf-8"), key_material)
|
|
463
|
+
data["content"] = encrypted.decode("utf-8")
|
|
464
|
+
data[_ENCRYPTED_FIELD] = True
|
|
465
|
+
except Exception as exc:
|
|
466
|
+
logger.warning("Encryption failed, storing plaintext: %s", exc)
|
|
467
|
+
|
|
468
|
+
return data
|
|
469
|
+
|
|
470
|
+
def _decrypt_content(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
471
|
+
"""Decrypt the content field using KMS Fernet key."""
|
|
472
|
+
try:
|
|
473
|
+
from .kms import KeyStore, _fernet_decrypt
|
|
474
|
+
|
|
475
|
+
store = KeyStore(self._home)
|
|
476
|
+
store.initialize()
|
|
477
|
+
key_record = store.derive_service_key("memory-fortress-enc")
|
|
478
|
+
key_material = store.get_key_material(key_record.key_id)
|
|
479
|
+
|
|
480
|
+
encrypted = data.get("content", "")
|
|
481
|
+
decrypted = _fernet_decrypt(encrypted.encode("utf-8"), key_material)
|
|
482
|
+
data["content"] = decrypted.decode("utf-8")
|
|
483
|
+
except Exception as exc:
|
|
484
|
+
logger.warning("Decryption failed: %s", exc)
|
|
485
|
+
data["content"] = "[DECRYPTION FAILED]"
|
|
486
|
+
|
|
487
|
+
return data
|
|
488
|
+
|
|
489
|
+
def _load_config(self) -> FortressConfig:
|
|
490
|
+
"""Load fortress config from disk or create default."""
|
|
491
|
+
config_path = self._home / "memory" / "fortress.json"
|
|
492
|
+
if config_path.exists():
|
|
493
|
+
try:
|
|
494
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
495
|
+
return FortressConfig.model_validate(data)
|
|
496
|
+
except (json.JSONDecodeError, OSError, ValueError) as exc:
|
|
497
|
+
logger.warning("Failed to load fortress config: %s", exc)
|
|
498
|
+
return FortressConfig(encryption_enabled=self._encryption_enabled)
|
|
499
|
+
|
|
500
|
+
def _save_config(self, config: FortressConfig) -> None:
|
|
501
|
+
"""Persist fortress config to disk."""
|
|
502
|
+
mem_dir = self._home / "memory"
|
|
503
|
+
mem_dir.mkdir(parents=True, exist_ok=True)
|
|
504
|
+
config_path = mem_dir / "fortress.json"
|
|
505
|
+
config_path.write_text(
|
|
506
|
+
config.model_dump_json(indent=2),
|
|
507
|
+
encoding="utf-8",
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
def _get_config(self) -> FortressConfig:
|
|
511
|
+
"""Get cached or load config."""
|
|
512
|
+
if self._config is None:
|
|
513
|
+
self._config = self._load_config()
|
|
514
|
+
return self._config
|
|
515
|
+
|
|
516
|
+
def _audit(self, event_type: str, detail: str, metadata: Optional[dict] = None) -> None:
|
|
517
|
+
"""Write an audit event if the security pillar is available."""
|
|
518
|
+
try:
|
|
519
|
+
from .pillars.security import audit_event
|
|
520
|
+
|
|
521
|
+
audit_event(
|
|
522
|
+
self._home,
|
|
523
|
+
event_type=event_type,
|
|
524
|
+
detail=detail,
|
|
525
|
+
agent="memory-fortress",
|
|
526
|
+
metadata=metadata,
|
|
527
|
+
)
|
|
528
|
+
except (ImportError, OSError) as exc:
|
|
529
|
+
logger.debug("Audit event skipped (%s): %s", event_type, exc)
|