@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,420 @@
|
|
|
1
|
+
"""Tests for the two-factor memory verification gate (memory_verifier.py).
|
|
2
|
+
|
|
3
|
+
These tests verify that:
|
|
4
|
+
- Aligned memories are promoted normally.
|
|
5
|
+
- Contradicting memories are tagged=conflicting, a conflict report is stored,
|
|
6
|
+
a critical notification fires, and promotion is skipped.
|
|
7
|
+
- Fail-open behaviour when skseed is unavailable.
|
|
8
|
+
- Short-term → mid-term gate in memory_engine._promote().
|
|
9
|
+
- Short-term → mid-term gate in PromotionEngine._promote().
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from unittest.mock import MagicMock, patch
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from skcapstone.memory_verifier import (
|
|
22
|
+
VerificationResult,
|
|
23
|
+
verify_before_promotion,
|
|
24
|
+
)
|
|
25
|
+
from skcapstone.models import MemoryEntry, MemoryLayer
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Helpers
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _make_entry(
|
|
34
|
+
memory_id: str = "abc123",
|
|
35
|
+
content: str = "The sky is blue",
|
|
36
|
+
layer: MemoryLayer = MemoryLayer.SHORT_TERM,
|
|
37
|
+
importance: float = 0.5,
|
|
38
|
+
tags: list[str] | None = None,
|
|
39
|
+
) -> MemoryEntry:
|
|
40
|
+
return MemoryEntry(
|
|
41
|
+
memory_id=memory_id,
|
|
42
|
+
content=content,
|
|
43
|
+
tags=tags or [],
|
|
44
|
+
source="test",
|
|
45
|
+
layer=layer,
|
|
46
|
+
importance=importance,
|
|
47
|
+
created_at=datetime.now(timezone.utc),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _aligned_result(coherence: float = 0.85) -> dict:
|
|
52
|
+
"""Simulate a truth_check return that passes alignment."""
|
|
53
|
+
return {
|
|
54
|
+
"is_aligned": True,
|
|
55
|
+
"collider_result": {
|
|
56
|
+
"coherence_score": coherence,
|
|
57
|
+
"truth_grade": "strong",
|
|
58
|
+
"collision_fragments": [],
|
|
59
|
+
},
|
|
60
|
+
"alignment_record": {},
|
|
61
|
+
"belief": {},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _contradicting_result(
|
|
66
|
+
coherence: float = 0.3,
|
|
67
|
+
fragments: list[str] | None = None,
|
|
68
|
+
) -> dict:
|
|
69
|
+
"""Simulate a truth_check return that fails alignment."""
|
|
70
|
+
return {
|
|
71
|
+
"is_aligned": False,
|
|
72
|
+
"collider_result": {
|
|
73
|
+
"coherence_score": coherence,
|
|
74
|
+
"truth_grade": "weak",
|
|
75
|
+
"collision_fragments": fragments or ["fragment A contradicts earlier claim"],
|
|
76
|
+
},
|
|
77
|
+
"alignment_record": {},
|
|
78
|
+
"belief": {},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.fixture
|
|
83
|
+
def home(tmp_path: Path) -> Path:
|
|
84
|
+
"""Minimal agent home directory."""
|
|
85
|
+
mem = tmp_path / "memory"
|
|
86
|
+
for layer in ("short-term", "mid-term", "long-term"):
|
|
87
|
+
(mem / layer).mkdir(parents=True)
|
|
88
|
+
return tmp_path
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# verify_before_promotion — unit tests
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestVerifyBeforePromotion:
|
|
97
|
+
|
|
98
|
+
def test_fail_open_when_skseed_missing(self, home: Path) -> None:
|
|
99
|
+
"""Should allow promotion when skseed cannot be imported."""
|
|
100
|
+
entry = _make_entry()
|
|
101
|
+
with patch.dict(sys.modules, {"skseed": None, "skseed.skill": None}):
|
|
102
|
+
result = verify_before_promotion(home, entry)
|
|
103
|
+
assert result.should_promote is True
|
|
104
|
+
assert result.is_conflicting is False
|
|
105
|
+
|
|
106
|
+
def test_fail_open_when_truth_check_raises(self, home: Path) -> None:
|
|
107
|
+
"""Should allow promotion when truth_check raises unexpectedly."""
|
|
108
|
+
import skcapstone.memory_verifier as mv
|
|
109
|
+
|
|
110
|
+
entry = _make_entry()
|
|
111
|
+
skill_mock = MagicMock()
|
|
112
|
+
skill_mock.truth_check = MagicMock(side_effect=RuntimeError("LLM timeout"))
|
|
113
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
114
|
+
result = mv.verify_before_promotion(home, entry)
|
|
115
|
+
assert result.should_promote is True
|
|
116
|
+
|
|
117
|
+
def test_skip_gate_for_mid_term_entries(self, home: Path) -> None:
|
|
118
|
+
"""Gate only fires for SHORT_TERM; mid-term entries pass through."""
|
|
119
|
+
entry = _make_entry(layer=MemoryLayer.MID_TERM)
|
|
120
|
+
result = verify_before_promotion(home, entry)
|
|
121
|
+
assert result.should_promote is True
|
|
122
|
+
assert result.is_conflicting is False
|
|
123
|
+
|
|
124
|
+
def test_aligned_memory_promotes(self, home: Path) -> None:
|
|
125
|
+
"""Aligned truth-check result → should_promote=True, no conflict."""
|
|
126
|
+
entry = _make_entry()
|
|
127
|
+
skill_mock = MagicMock()
|
|
128
|
+
skill_mock.truth_check = MagicMock(return_value=_aligned_result(0.9))
|
|
129
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
130
|
+
result = verify_before_promotion(home, entry)
|
|
131
|
+
assert result.should_promote is True
|
|
132
|
+
assert result.is_conflicting is False
|
|
133
|
+
assert result.coherence_score == pytest.approx(0.9)
|
|
134
|
+
assert result.truth_grade == "strong"
|
|
135
|
+
|
|
136
|
+
def test_contradicting_memory_blocked(self, home: Path) -> None:
|
|
137
|
+
"""Contradiction found → should_promote=False, is_conflicting=True."""
|
|
138
|
+
entry = _make_entry()
|
|
139
|
+
skill_mock = MagicMock()
|
|
140
|
+
skill_mock.truth_check = MagicMock(return_value=_contradicting_result())
|
|
141
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
142
|
+
with patch("skcapstone.memory_verifier._fire_conflict_notification"):
|
|
143
|
+
result = verify_before_promotion(home, entry)
|
|
144
|
+
assert result.should_promote is False
|
|
145
|
+
assert result.is_conflicting is True
|
|
146
|
+
assert result.collision_fragments != []
|
|
147
|
+
|
|
148
|
+
def test_conflicting_tag_added_to_candidate(self, home: Path) -> None:
|
|
149
|
+
"""Candidate entry gets tag=conflicting when promotion is blocked."""
|
|
150
|
+
entry = _make_entry(memory_id="cand01")
|
|
151
|
+
# Write the entry to disk so _save_entry works
|
|
152
|
+
path = home / "memory" / "short-term" / "cand01.json"
|
|
153
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
154
|
+
|
|
155
|
+
skill_mock = MagicMock()
|
|
156
|
+
skill_mock.truth_check = MagicMock(return_value=_contradicting_result())
|
|
157
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
158
|
+
with patch("skcapstone.memory_verifier._fire_conflict_notification"):
|
|
159
|
+
verify_before_promotion(home, entry)
|
|
160
|
+
|
|
161
|
+
# Re-read from disk
|
|
162
|
+
import json
|
|
163
|
+
raw = json.loads(path.read_text())
|
|
164
|
+
assert "conflicting" in raw["tags"]
|
|
165
|
+
|
|
166
|
+
def test_conflict_report_stored(self, home: Path) -> None:
|
|
167
|
+
"""A conflict-report memory is stored in short-term when blocked."""
|
|
168
|
+
entry = _make_entry(memory_id="cand02", content="contradictory claim")
|
|
169
|
+
path = home / "memory" / "short-term" / "cand02.json"
|
|
170
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
171
|
+
|
|
172
|
+
skill_mock = MagicMock()
|
|
173
|
+
skill_mock.truth_check = MagicMock(return_value=_contradicting_result())
|
|
174
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
175
|
+
with patch("skcapstone.memory_verifier._fire_conflict_notification"):
|
|
176
|
+
result = verify_before_promotion(home, entry)
|
|
177
|
+
|
|
178
|
+
assert result.conflict_report_id is not None
|
|
179
|
+
# The report file should exist in short-term
|
|
180
|
+
report_path = home / "memory" / "short-term" / f"{result.conflict_report_id}.json"
|
|
181
|
+
assert report_path.exists()
|
|
182
|
+
import json
|
|
183
|
+
report = json.loads(report_path.read_text())
|
|
184
|
+
assert "conflicting" in report["tags"]
|
|
185
|
+
assert "CONFLICT REPORT" in report["content"]
|
|
186
|
+
assert "cand02" in report["content"]
|
|
187
|
+
|
|
188
|
+
def test_critical_notification_fired(self, home: Path) -> None:
|
|
189
|
+
"""A critical desktop notification fires when promotion is blocked."""
|
|
190
|
+
entry = _make_entry()
|
|
191
|
+
path = home / "memory" / "short-term" / f"{entry.memory_id}.json"
|
|
192
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
193
|
+
|
|
194
|
+
skill_mock = MagicMock()
|
|
195
|
+
skill_mock.truth_check = MagicMock(return_value=_contradicting_result())
|
|
196
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
197
|
+
with patch("skcapstone.memory_verifier._fire_conflict_notification") as mock_notif:
|
|
198
|
+
verify_before_promotion(home, entry)
|
|
199
|
+
mock_notif.assert_called_once()
|
|
200
|
+
|
|
201
|
+
def test_notification_uses_critical_urgency(self, home: Path) -> None:
|
|
202
|
+
"""The actual notify() call receives urgency=critical."""
|
|
203
|
+
entry = _make_entry(memory_id="notif01")
|
|
204
|
+
path = home / "memory" / "short-term" / "notif01.json"
|
|
205
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
206
|
+
|
|
207
|
+
skill_mock = MagicMock()
|
|
208
|
+
skill_mock.truth_check = MagicMock(return_value=_contradicting_result())
|
|
209
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
210
|
+
with patch("skcapstone.notifications.notify") as mock_notify:
|
|
211
|
+
import skcapstone.memory_verifier as mv
|
|
212
|
+
# Ensure the module uses our patched notify
|
|
213
|
+
with patch.object(mv, "_fire_conflict_notification",
|
|
214
|
+
wraps=mv._fire_conflict_notification):
|
|
215
|
+
verify_before_promotion(home, entry)
|
|
216
|
+
# At least one call should have urgency=critical
|
|
217
|
+
calls = mock_notify.call_args_list
|
|
218
|
+
assert any(
|
|
219
|
+
call.kwargs.get("urgency") == "critical" or
|
|
220
|
+
(len(call.args) >= 3 and call.args[2] == "critical")
|
|
221
|
+
for call in calls
|
|
222
|
+
), f"No critical notification call found in {calls}"
|
|
223
|
+
|
|
224
|
+
def test_verifier_source_bypasses_gate(self, home: Path) -> None:
|
|
225
|
+
"""Conflict-report entries (source=memory_verifier) must not be re-checked."""
|
|
226
|
+
entry = _make_entry(memory_id="meta01")
|
|
227
|
+
entry = _make_entry(memory_id="meta01")
|
|
228
|
+
# Simulate a conflict-report entry
|
|
229
|
+
from dataclasses import replace
|
|
230
|
+
entry2 = MemoryEntry(
|
|
231
|
+
memory_id="meta01",
|
|
232
|
+
content="[CONFLICT REPORT] some conflict",
|
|
233
|
+
tags=["conflicting"],
|
|
234
|
+
source="memory_verifier",
|
|
235
|
+
layer=MemoryLayer.SHORT_TERM,
|
|
236
|
+
importance=0.8,
|
|
237
|
+
created_at=entry.created_at,
|
|
238
|
+
)
|
|
239
|
+
# truth_check should never be called
|
|
240
|
+
skill_mock = MagicMock()
|
|
241
|
+
skill_mock.truth_check = MagicMock(side_effect=AssertionError("should not be called"))
|
|
242
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
243
|
+
result = verify_before_promotion(home, entry2)
|
|
244
|
+
assert result.should_promote is True
|
|
245
|
+
skill_mock.truth_check.assert_not_called()
|
|
246
|
+
|
|
247
|
+
def test_low_coherence_no_fragments_also_blocked(self, home: Path) -> None:
|
|
248
|
+
"""is_aligned=False with empty fragments still triggers a conflict."""
|
|
249
|
+
entry = _make_entry()
|
|
250
|
+
skill_mock = MagicMock()
|
|
251
|
+
# No fragments, but is_aligned=False
|
|
252
|
+
skill_mock.truth_check = MagicMock(return_value={
|
|
253
|
+
"is_aligned": False,
|
|
254
|
+
"collider_result": {
|
|
255
|
+
"coherence_score": 0.4,
|
|
256
|
+
"truth_grade": "weak",
|
|
257
|
+
"collision_fragments": [],
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
with patch.dict(sys.modules, {"skseed": MagicMock(), "skseed.skill": skill_mock}):
|
|
261
|
+
with patch("skcapstone.memory_verifier._fire_conflict_notification"):
|
|
262
|
+
result = verify_before_promotion(home, entry)
|
|
263
|
+
assert result.should_promote is False
|
|
264
|
+
assert result.is_conflicting is True
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# Integration: memory_engine._promote() gate
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class TestMemoryEnginePromoteGate:
|
|
273
|
+
|
|
274
|
+
def test_engine_promote_short_term_blocked(self, home: Path) -> None:
|
|
275
|
+
"""_promote() in memory_engine stays in short-term when gate says no."""
|
|
276
|
+
from skcapstone import memory_engine
|
|
277
|
+
|
|
278
|
+
entry = _make_entry(memory_id="eng01")
|
|
279
|
+
path = home / "memory" / "short-term" / "eng01.json"
|
|
280
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
281
|
+
|
|
282
|
+
def _fake_verify(h, e):
|
|
283
|
+
return VerificationResult(
|
|
284
|
+
should_promote=False,
|
|
285
|
+
is_conflicting=True,
|
|
286
|
+
coherence_score=0.3,
|
|
287
|
+
truth_grade="weak",
|
|
288
|
+
collision_fragments=["contradiction"],
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Patch at the verifier module level (local import picks it up there)
|
|
292
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
293
|
+
promoted = memory_engine._promote(home, entry, path)
|
|
294
|
+
|
|
295
|
+
assert promoted is False
|
|
296
|
+
# Entry should still be in short-term
|
|
297
|
+
assert path.exists()
|
|
298
|
+
|
|
299
|
+
def test_engine_promote_short_term_allowed(self, home: Path) -> None:
|
|
300
|
+
"""_promote() in memory_engine moves to mid-term when gate passes."""
|
|
301
|
+
from skcapstone import memory_engine
|
|
302
|
+
|
|
303
|
+
entry = _make_entry(memory_id="eng02")
|
|
304
|
+
path = home / "memory" / "short-term" / "eng02.json"
|
|
305
|
+
path.write_text(entry.model_dump_json(), encoding="utf-8")
|
|
306
|
+
|
|
307
|
+
def _fake_verify(h, e):
|
|
308
|
+
return VerificationResult(should_promote=True, coherence_score=0.9)
|
|
309
|
+
|
|
310
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
311
|
+
promoted = memory_engine._promote(home, entry, path)
|
|
312
|
+
|
|
313
|
+
assert promoted is True
|
|
314
|
+
mid_path = home / "memory" / "mid-term" / "eng02.json"
|
|
315
|
+
assert mid_path.exists()
|
|
316
|
+
|
|
317
|
+
def test_engine_store_fast_path_blocked(self, home: Path) -> None:
|
|
318
|
+
"""store() with importance>=0.7 stays in short-term when gate says no."""
|
|
319
|
+
from skcapstone import memory_engine
|
|
320
|
+
|
|
321
|
+
def _fake_verify(h, e):
|
|
322
|
+
return VerificationResult(should_promote=False, is_conflicting=True)
|
|
323
|
+
|
|
324
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
325
|
+
result = memory_engine.store(
|
|
326
|
+
home=home,
|
|
327
|
+
content="Important claim",
|
|
328
|
+
importance=0.8,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
assert result.layer == MemoryLayer.SHORT_TERM
|
|
332
|
+
|
|
333
|
+
def test_engine_store_fast_path_allowed(self, home: Path) -> None:
|
|
334
|
+
"""store() with importance>=0.7 promotes to mid-term when gate passes."""
|
|
335
|
+
from skcapstone import memory_engine
|
|
336
|
+
|
|
337
|
+
def _fake_verify(h, e):
|
|
338
|
+
return VerificationResult(should_promote=True, coherence_score=0.9)
|
|
339
|
+
|
|
340
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
341
|
+
result = memory_engine.store(
|
|
342
|
+
home=home,
|
|
343
|
+
content="Important aligned claim",
|
|
344
|
+
importance=0.8,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
assert result.layer == MemoryLayer.MID_TERM
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
# Integration: PromotionEngine._promote() gate
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class TestPromotionEngineGate:
|
|
356
|
+
|
|
357
|
+
def test_promoter_sweep_blocked_counts_as_skipped(self, home: Path) -> None:
|
|
358
|
+
"""A truth-check-blocked candidate is not in result.promoted."""
|
|
359
|
+
from datetime import timedelta
|
|
360
|
+
|
|
361
|
+
from skcapstone.memory_promoter import PromotionEngine, PromotionThresholds
|
|
362
|
+
|
|
363
|
+
# Write a short-term entry that scores well above threshold
|
|
364
|
+
from skcapstone.memory_engine import _save_entry
|
|
365
|
+
|
|
366
|
+
created = datetime.now(timezone.utc) - timedelta(hours=30)
|
|
367
|
+
entry = MemoryEntry(
|
|
368
|
+
memory_id="sweep01",
|
|
369
|
+
content="decision: use sovereign architecture",
|
|
370
|
+
tags=["architect", "decision", "breakthrough"],
|
|
371
|
+
source="test",
|
|
372
|
+
layer=MemoryLayer.SHORT_TERM,
|
|
373
|
+
importance=0.9,
|
|
374
|
+
access_count=5,
|
|
375
|
+
created_at=created,
|
|
376
|
+
)
|
|
377
|
+
_save_entry(home, entry)
|
|
378
|
+
|
|
379
|
+
def _fake_verify(h, e):
|
|
380
|
+
return VerificationResult(should_promote=False, is_conflicting=True)
|
|
381
|
+
|
|
382
|
+
engine = PromotionEngine(home, PromotionThresholds(short_to_mid=0.1))
|
|
383
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
384
|
+
result = engine.sweep(dry_run=False)
|
|
385
|
+
|
|
386
|
+
# Candidate was found but not promoted
|
|
387
|
+
assert not any(c.memory_id == "sweep01" and c.promoted for c in result.candidates)
|
|
388
|
+
|
|
389
|
+
def test_promoter_sweep_allowed_promotes(self, home: Path) -> None:
|
|
390
|
+
"""A truth-check-allowed candidate ends up in mid-term."""
|
|
391
|
+
from datetime import timedelta
|
|
392
|
+
|
|
393
|
+
from skcapstone.memory_promoter import PromotionEngine, PromotionThresholds
|
|
394
|
+
from skcapstone.memory_engine import _save_entry
|
|
395
|
+
|
|
396
|
+
created = datetime.now(timezone.utc) - timedelta(hours=30)
|
|
397
|
+
entry = MemoryEntry(
|
|
398
|
+
memory_id="sweep02",
|
|
399
|
+
content="decision: sovereign architecture approved",
|
|
400
|
+
tags=["architect", "decision"],
|
|
401
|
+
source="test",
|
|
402
|
+
layer=MemoryLayer.SHORT_TERM,
|
|
403
|
+
importance=0.9,
|
|
404
|
+
access_count=5,
|
|
405
|
+
created_at=created,
|
|
406
|
+
)
|
|
407
|
+
_save_entry(home, entry)
|
|
408
|
+
|
|
409
|
+
def _fake_verify(h, e):
|
|
410
|
+
return VerificationResult(should_promote=True, coherence_score=0.95)
|
|
411
|
+
|
|
412
|
+
engine = PromotionEngine(home, PromotionThresholds(short_to_mid=0.1))
|
|
413
|
+
# Sweep short-term only so the entry isn't double-promoted in one pass
|
|
414
|
+
with patch("skcapstone.memory_verifier.verify_before_promotion", _fake_verify):
|
|
415
|
+
result = engine.sweep(layer=MemoryLayer.SHORT_TERM, dry_run=False)
|
|
416
|
+
|
|
417
|
+
promoted_ids = [c.memory_id for c in result.promoted if c.promoted]
|
|
418
|
+
assert "sweep02" in promoted_ids
|
|
419
|
+
mid_path = home / "memory" / "mid-term" / "sweep02.json"
|
|
420
|
+
assert mid_path.exists()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Tests for skcapstone.message_crypto — AES-256-GCM message encryption.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- encrypt_message / decrypt_message happy-path roundtrip
|
|
5
|
+
- Wrong key raises InvalidTag (authentication failure)
|
|
6
|
+
- Tampered ciphertext raises InvalidTag
|
|
7
|
+
- Short key raises ValueError
|
|
8
|
+
- pack_encrypted / unpack_encrypted envelope helpers
|
|
9
|
+
- is_encrypted_content detection
|
|
10
|
+
- decrypt_content pass-through for plaintext
|
|
11
|
+
- KMS-backed encrypt_content / decrypt_content roundtrip
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import pytest
|
|
21
|
+
|
|
22
|
+
from skcapstone.message_crypto import (
|
|
23
|
+
decrypt_content,
|
|
24
|
+
decrypt_message,
|
|
25
|
+
encrypt_content,
|
|
26
|
+
encrypt_message,
|
|
27
|
+
is_encrypted_content,
|
|
28
|
+
pack_encrypted,
|
|
29
|
+
unpack_encrypted,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Fixtures
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def aes_key() -> bytes:
|
|
40
|
+
"""Return a random 32-byte AES key for testing."""
|
|
41
|
+
return os.urandom(32)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def kms_home(tmp_path: Path) -> Path:
|
|
46
|
+
"""Create a minimal agent home with identity for KMS key derivation."""
|
|
47
|
+
identity_dir = tmp_path / "identity"
|
|
48
|
+
identity_dir.mkdir(parents=True)
|
|
49
|
+
identity = {
|
|
50
|
+
"name": "test-agent",
|
|
51
|
+
"email": "test@skcapstone.local",
|
|
52
|
+
"fingerprint": "DEADBEEF1234567890ABCDEF1234567890ABCDEF",
|
|
53
|
+
"capauth_managed": False,
|
|
54
|
+
}
|
|
55
|
+
(identity_dir / "identity.json").write_text(json.dumps(identity), encoding="utf-8")
|
|
56
|
+
(tmp_path / "security").mkdir(parents=True)
|
|
57
|
+
return tmp_path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Low-level encrypt/decrypt
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_encrypt_decrypt_roundtrip(aes_key):
|
|
66
|
+
"""Happy path: encrypt then decrypt returns the original plaintext."""
|
|
67
|
+
plaintext = "Hello, sovereign world! 🔒"
|
|
68
|
+
token = encrypt_message(plaintext, aes_key)
|
|
69
|
+
assert isinstance(token, str)
|
|
70
|
+
assert len(token) > 0
|
|
71
|
+
result = decrypt_message(token, aes_key)
|
|
72
|
+
assert result == plaintext
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_encrypt_produces_different_ciphertext_each_call(aes_key):
|
|
76
|
+
"""Each encryption call uses a fresh nonce → different output."""
|
|
77
|
+
msg = "repeat me"
|
|
78
|
+
t1 = encrypt_message(msg, aes_key)
|
|
79
|
+
t2 = encrypt_message(msg, aes_key)
|
|
80
|
+
assert t1 != t2, "Nonces must differ — ciphertexts should not be identical"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_wrong_key_raises_on_decrypt(aes_key):
|
|
84
|
+
"""Decrypting with a different key raises InvalidTag (AES-GCM auth failure)."""
|
|
85
|
+
from cryptography.exceptions import InvalidTag
|
|
86
|
+
|
|
87
|
+
token = encrypt_message("secret", aes_key)
|
|
88
|
+
wrong_key = os.urandom(32)
|
|
89
|
+
with pytest.raises(InvalidTag):
|
|
90
|
+
decrypt_message(token, wrong_key)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_tampered_ciphertext_raises(aes_key):
|
|
94
|
+
"""Bit-flipping the ciphertext causes authentication to fail."""
|
|
95
|
+
import base64
|
|
96
|
+
from cryptography.exceptions import InvalidTag
|
|
97
|
+
|
|
98
|
+
token = encrypt_message("tamper me", aes_key)
|
|
99
|
+
raw = bytearray(base64.b64decode(token))
|
|
100
|
+
raw[-1] ^= 0xFF # flip the last byte (inside the GCM tag)
|
|
101
|
+
bad_token = base64.b64encode(bytes(raw)).decode("ascii")
|
|
102
|
+
with pytest.raises(InvalidTag):
|
|
103
|
+
decrypt_message(bad_token, aes_key)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_short_key_raises_value_error():
|
|
107
|
+
"""A key shorter than 32 bytes should raise ValueError immediately."""
|
|
108
|
+
with pytest.raises(ValueError, match="32-byte"):
|
|
109
|
+
encrypt_message("oops", b"too-short")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_short_key_decrypt_raises_value_error():
|
|
113
|
+
"""decrypt_message with a short key raises ValueError."""
|
|
114
|
+
with pytest.raises(ValueError, match="32-byte"):
|
|
115
|
+
decrypt_message("sometoken", b"too-short")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Envelope helpers
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_pack_unpack_roundtrip(aes_key):
|
|
124
|
+
"""pack_encrypted wraps token; unpack_encrypted recovers it."""
|
|
125
|
+
token = encrypt_message("envelope test", aes_key)
|
|
126
|
+
envelope = pack_encrypted(token)
|
|
127
|
+
data = json.loads(envelope)
|
|
128
|
+
assert data["skchat_encrypted"] is True
|
|
129
|
+
assert data["v"] == 1
|
|
130
|
+
assert data["ciphertext"] == token
|
|
131
|
+
recovered = unpack_encrypted(envelope)
|
|
132
|
+
assert recovered == token
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_unpack_returns_none_for_plaintext():
|
|
136
|
+
"""unpack_encrypted on plain text returns None."""
|
|
137
|
+
assert unpack_encrypted("just a normal message") is None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_unpack_returns_none_for_invalid_json():
|
|
141
|
+
"""unpack_encrypted on malformed JSON returns None."""
|
|
142
|
+
assert unpack_encrypted("{not valid json") is None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_is_encrypted_content_true(aes_key):
|
|
146
|
+
"""is_encrypted_content returns True for a proper envelope."""
|
|
147
|
+
token = encrypt_message("check me", aes_key)
|
|
148
|
+
envelope = pack_encrypted(token)
|
|
149
|
+
assert is_encrypted_content(envelope) is True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_is_encrypted_content_false_for_plaintext():
|
|
153
|
+
"""is_encrypted_content returns False for plain strings."""
|
|
154
|
+
assert is_encrypted_content("hello world") is False
|
|
155
|
+
assert is_encrypted_content("") is False
|
|
156
|
+
assert is_encrypted_content('{"some": "json"}') is False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# KMS-backed API
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_kms_encrypt_decrypt_roundtrip(kms_home):
|
|
165
|
+
"""encrypt_content → decrypt_content using KMS-derived key roundtrips cleanly."""
|
|
166
|
+
plaintext = "sovereign message via KMS"
|
|
167
|
+
envelope = encrypt_content(plaintext, kms_home)
|
|
168
|
+
assert is_encrypted_content(envelope), "Output must be an encrypted envelope"
|
|
169
|
+
result = decrypt_content(envelope, kms_home)
|
|
170
|
+
assert result == plaintext
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_decrypt_content_passthrough_for_plaintext(kms_home):
|
|
174
|
+
"""decrypt_content leaves non-encrypted content unchanged."""
|
|
175
|
+
plain = "nothing to see here"
|
|
176
|
+
result = decrypt_content(plain, kms_home)
|
|
177
|
+
assert result == plain
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_kms_same_key_produced_across_calls(kms_home):
|
|
181
|
+
"""Two derive_chat_key calls return identical material (deterministic HKDF)."""
|
|
182
|
+
from skcapstone.message_crypto import derive_chat_key
|
|
183
|
+
|
|
184
|
+
key1 = derive_chat_key(kms_home)
|
|
185
|
+
key2 = derive_chat_key(kms_home)
|
|
186
|
+
assert key1 == key2, "HKDF derivation must be deterministic for same identity"
|
|
187
|
+
assert len(key1) == 32
|