@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,495 @@
|
|
|
1
|
+
"""Tests for SKSecurity KMS — sovereign key management.
|
|
2
|
+
|
|
3
|
+
Tests the skcapstone KMS wrapper which delegates crypto operations
|
|
4
|
+
to sksecurity.kms (AES-256-GCM key wrapping, HKDF-SHA256 derivation).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from skcapstone.kms import (
|
|
16
|
+
KeyRecord,
|
|
17
|
+
KeyStatus,
|
|
18
|
+
KeyStore,
|
|
19
|
+
KeyType,
|
|
20
|
+
RotationEntry,
|
|
21
|
+
_decrypt_at_rest,
|
|
22
|
+
_derive_key,
|
|
23
|
+
_encrypt_at_rest,
|
|
24
|
+
_key_fingerprint,
|
|
25
|
+
_key_id,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def home(tmp_path: Path) -> Path:
|
|
31
|
+
"""Create a minimal agent home for KMS tests."""
|
|
32
|
+
identity_dir = tmp_path / "identity"
|
|
33
|
+
identity_dir.mkdir()
|
|
34
|
+
manifest = {
|
|
35
|
+
"name": "test-agent",
|
|
36
|
+
"email": "test@skcapstone.local",
|
|
37
|
+
"fingerprint": "ABCD1234567890ABCDEF1234567890ABCDEF1234",
|
|
38
|
+
"capauth_managed": False,
|
|
39
|
+
}
|
|
40
|
+
(identity_dir / "identity.json").write_text(json.dumps(manifest), encoding="utf-8")
|
|
41
|
+
|
|
42
|
+
security_dir = tmp_path / "security"
|
|
43
|
+
security_dir.mkdir()
|
|
44
|
+
|
|
45
|
+
return tmp_path
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def store(home: Path) -> KeyStore:
|
|
50
|
+
"""Create and initialize a KeyStore."""
|
|
51
|
+
ks = KeyStore(home)
|
|
52
|
+
ks.initialize()
|
|
53
|
+
return ks
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Crypto helper tests
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestCryptoHelpers:
|
|
62
|
+
"""Tests for low-level cryptographic helpers."""
|
|
63
|
+
|
|
64
|
+
def test_derive_key_deterministic(self) -> None:
|
|
65
|
+
"""Same inputs produce same output."""
|
|
66
|
+
k1 = _derive_key(b"master", b"info")
|
|
67
|
+
k2 = _derive_key(b"master", b"info")
|
|
68
|
+
assert k1 == k2
|
|
69
|
+
|
|
70
|
+
def test_derive_key_different_info(self) -> None:
|
|
71
|
+
"""Different info strings produce different keys."""
|
|
72
|
+
k1 = _derive_key(b"master", b"service-a")
|
|
73
|
+
k2 = _derive_key(b"master", b"service-b")
|
|
74
|
+
assert k1 != k2
|
|
75
|
+
|
|
76
|
+
def test_derive_key_length(self) -> None:
|
|
77
|
+
"""Output length matches requested."""
|
|
78
|
+
k16 = _derive_key(b"master", b"info", length=16)
|
|
79
|
+
k64 = _derive_key(b"master", b"info", length=64)
|
|
80
|
+
assert len(k16) == 16
|
|
81
|
+
assert len(k64) == 64
|
|
82
|
+
|
|
83
|
+
def test_aes_gcm_roundtrip(self) -> None:
|
|
84
|
+
"""Encrypt then decrypt returns original plaintext."""
|
|
85
|
+
key = _derive_key(b"test", b"enc", length=32)
|
|
86
|
+
plaintext = b"sovereign secrets"
|
|
87
|
+
ct = _encrypt_at_rest(plaintext, key)
|
|
88
|
+
assert _decrypt_at_rest(ct, key) == plaintext
|
|
89
|
+
|
|
90
|
+
def test_aes_gcm_ciphertext_format(self) -> None:
|
|
91
|
+
"""AES-256-GCM output is nonce (12) + ciphertext + tag (16)."""
|
|
92
|
+
key = os.urandom(32)
|
|
93
|
+
plaintext = b"test data"
|
|
94
|
+
ct = _encrypt_at_rest(plaintext, key)
|
|
95
|
+
# nonce=12, plaintext_len=9, tag=16 → total=37
|
|
96
|
+
assert len(ct) == 12 + len(plaintext) + 16
|
|
97
|
+
|
|
98
|
+
def test_aes_gcm_wrong_key_fails(self) -> None:
|
|
99
|
+
"""Decrypting with wrong key raises an error."""
|
|
100
|
+
key1 = _derive_key(b"key1", b"enc", length=32)
|
|
101
|
+
key2 = _derive_key(b"key2", b"enc", length=32)
|
|
102
|
+
ct = _encrypt_at_rest(b"data", key1)
|
|
103
|
+
with pytest.raises(Exception):
|
|
104
|
+
_decrypt_at_rest(ct, key2)
|
|
105
|
+
|
|
106
|
+
def test_key_fingerprint_deterministic(self) -> None:
|
|
107
|
+
"""Same material produces same fingerprint."""
|
|
108
|
+
fp1 = _key_fingerprint(b"material")
|
|
109
|
+
fp2 = _key_fingerprint(b"material")
|
|
110
|
+
assert fp1 == fp2
|
|
111
|
+
assert len(fp1) == 64 # SHA-256 hex
|
|
112
|
+
|
|
113
|
+
def test_key_id_deterministic(self) -> None:
|
|
114
|
+
"""Same inputs produce same key_id."""
|
|
115
|
+
id1 = _key_id("label", KeyType.SERVICE)
|
|
116
|
+
id2 = _key_id("label", KeyType.SERVICE)
|
|
117
|
+
assert id1 == id2
|
|
118
|
+
assert len(id1) == 16
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# KeyStore initialization
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestKeyStoreInit:
|
|
127
|
+
"""Tests for KMS initialization."""
|
|
128
|
+
|
|
129
|
+
def test_initialize_creates_master(self, home: Path) -> None:
|
|
130
|
+
"""Initialize creates a master key."""
|
|
131
|
+
store = KeyStore(home)
|
|
132
|
+
master = store.initialize()
|
|
133
|
+
assert master.key_type == KeyType.MASTER
|
|
134
|
+
assert master.status == KeyStatus.ACTIVE
|
|
135
|
+
assert master.label == "master"
|
|
136
|
+
|
|
137
|
+
def test_initialize_idempotent(self, store: KeyStore) -> None:
|
|
138
|
+
"""Calling initialize twice returns same master."""
|
|
139
|
+
m1 = store.initialize()
|
|
140
|
+
m2 = store.initialize()
|
|
141
|
+
assert m1.key_id == m2.key_id
|
|
142
|
+
assert m1.fingerprint == m2.fingerprint
|
|
143
|
+
|
|
144
|
+
def test_initialize_creates_directory_structure(self, home: Path) -> None:
|
|
145
|
+
"""KMS directories are created on init."""
|
|
146
|
+
KeyStore(home).initialize()
|
|
147
|
+
assert (home / "security" / "kms").is_dir()
|
|
148
|
+
assert (home / "security" / "kms" / "keys").is_dir()
|
|
149
|
+
assert (home / "security" / "kms" / "keystore.json").exists()
|
|
150
|
+
|
|
151
|
+
def test_initialize_without_identity(self, tmp_path: Path) -> None:
|
|
152
|
+
"""KMS works with random seed when no identity exists."""
|
|
153
|
+
store = KeyStore(tmp_path)
|
|
154
|
+
master = store.initialize()
|
|
155
|
+
assert master.key_type == KeyType.MASTER
|
|
156
|
+
assert master.status == KeyStatus.ACTIVE
|
|
157
|
+
|
|
158
|
+
def test_algorithm_uses_aes_256_gcm(self, store: KeyStore) -> None:
|
|
159
|
+
"""Default algorithm is HKDF-SHA256+AES-256-GCM."""
|
|
160
|
+
key = store.derive_service_key("test-algo")
|
|
161
|
+
assert "AES-256-GCM" in key.algorithm
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# Service key derivation
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TestServiceKeys:
|
|
170
|
+
"""Tests for service key derivation."""
|
|
171
|
+
|
|
172
|
+
def test_derive_service_key(self, store: KeyStore) -> None:
|
|
173
|
+
"""Derive a service key from master."""
|
|
174
|
+
key = store.derive_service_key("api-gateway")
|
|
175
|
+
assert key.key_type == KeyType.SERVICE
|
|
176
|
+
assert key.label == "api-gateway"
|
|
177
|
+
assert key.status == KeyStatus.ACTIVE
|
|
178
|
+
assert key.parent_key_id is not None
|
|
179
|
+
|
|
180
|
+
def test_service_key_idempotent(self, store: KeyStore) -> None:
|
|
181
|
+
"""Same service name returns existing key."""
|
|
182
|
+
k1 = store.derive_service_key("skchat")
|
|
183
|
+
k2 = store.derive_service_key("skchat")
|
|
184
|
+
assert k1.key_id == k2.key_id
|
|
185
|
+
|
|
186
|
+
def test_different_services_different_keys(self, store: KeyStore) -> None:
|
|
187
|
+
"""Different services produce different keys."""
|
|
188
|
+
k1 = store.derive_service_key("service-a")
|
|
189
|
+
k2 = store.derive_service_key("service-b")
|
|
190
|
+
assert k1.fingerprint != k2.fingerprint
|
|
191
|
+
|
|
192
|
+
def test_service_key_with_ttl(self, store: KeyStore) -> None:
|
|
193
|
+
"""Service key with TTL has expiry set."""
|
|
194
|
+
key = store.derive_service_key("temp-service", ttl_days=30)
|
|
195
|
+
assert key.expires_at is not None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# Subkey derivation
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TestSubkeys:
|
|
204
|
+
"""Tests for subkey derivation."""
|
|
205
|
+
|
|
206
|
+
def test_derive_subkey_from_master(self, store: KeyStore) -> None:
|
|
207
|
+
"""Derive a subkey from the master key."""
|
|
208
|
+
key = store.derive_subkey("delegation-a")
|
|
209
|
+
assert key.key_type == KeyType.SUBKEY
|
|
210
|
+
assert key.label == "delegation-a"
|
|
211
|
+
|
|
212
|
+
def test_derive_subkey_from_service(self, store: KeyStore) -> None:
|
|
213
|
+
"""Derive a subkey from a service key."""
|
|
214
|
+
store.derive_service_key("parent-service")
|
|
215
|
+
key = store.derive_subkey("child", parent_label="parent-service")
|
|
216
|
+
assert key.key_type == KeyType.SUBKEY
|
|
217
|
+
assert key.parent_key_id is not None
|
|
218
|
+
|
|
219
|
+
def test_subkey_from_missing_parent_raises(self, store: KeyStore) -> None:
|
|
220
|
+
"""Deriving from a nonexistent parent raises ValueError."""
|
|
221
|
+
with pytest.raises(ValueError, match="not found"):
|
|
222
|
+
store.derive_subkey("orphan", parent_label="nonexistent")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# Team key management
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestTeamKeys:
|
|
231
|
+
"""Tests for team key creation and member management."""
|
|
232
|
+
|
|
233
|
+
def test_create_team_key(self, store: KeyStore) -> None:
|
|
234
|
+
"""Create a team key with members."""
|
|
235
|
+
key = store.create_team_key("dev-team", members=["opus", "lumina"])
|
|
236
|
+
assert key.key_type == KeyType.TEAM
|
|
237
|
+
assert key.label == "dev-team"
|
|
238
|
+
assert key.members == ["opus", "lumina"]
|
|
239
|
+
|
|
240
|
+
def test_team_key_idempotent(self, store: KeyStore) -> None:
|
|
241
|
+
"""Same team name returns existing key."""
|
|
242
|
+
k1 = store.create_team_key("team-x")
|
|
243
|
+
k2 = store.create_team_key("team-x")
|
|
244
|
+
assert k1.key_id == k2.key_id
|
|
245
|
+
|
|
246
|
+
def test_add_member(self, store: KeyStore) -> None:
|
|
247
|
+
"""Add a member to a team key."""
|
|
248
|
+
store.create_team_key("team-y", members=["opus"])
|
|
249
|
+
updated = store.add_team_member("team-y", "grok")
|
|
250
|
+
assert "grok" in updated.members
|
|
251
|
+
|
|
252
|
+
def test_add_duplicate_member_noop(self, store: KeyStore) -> None:
|
|
253
|
+
"""Adding an existing member is a no-op."""
|
|
254
|
+
store.create_team_key("team-z", members=["opus"])
|
|
255
|
+
updated = store.add_team_member("team-z", "opus")
|
|
256
|
+
assert updated.members.count("opus") == 1
|
|
257
|
+
|
|
258
|
+
def test_remove_member(self, store: KeyStore) -> None:
|
|
259
|
+
"""Remove a member from a team key."""
|
|
260
|
+
store.create_team_key("team-w", members=["opus", "lumina", "grok"])
|
|
261
|
+
updated = store.remove_team_member("team-w", "lumina")
|
|
262
|
+
assert "lumina" not in updated.members
|
|
263
|
+
assert "opus" in updated.members
|
|
264
|
+
|
|
265
|
+
def test_add_member_missing_team_raises(self, store: KeyStore) -> None:
|
|
266
|
+
"""Adding to a nonexistent team raises ValueError."""
|
|
267
|
+
with pytest.raises(ValueError, match="not found"):
|
|
268
|
+
store.add_team_member("ghost-team", "opus")
|
|
269
|
+
|
|
270
|
+
def test_team_acl_grants_access(self, store: KeyStore) -> None:
|
|
271
|
+
"""Team member can access key material."""
|
|
272
|
+
key = store.create_team_key("acl-team", members=["opus"])
|
|
273
|
+
material = store.get_key_material(key.key_id, agent_name="opus")
|
|
274
|
+
assert len(material) == 32
|
|
275
|
+
|
|
276
|
+
def test_team_acl_denies_access(self, store: KeyStore) -> None:
|
|
277
|
+
"""Non-member is denied access to team key material."""
|
|
278
|
+
key = store.create_team_key("private-team", members=["opus"])
|
|
279
|
+
with pytest.raises(PermissionError, match="not in team"):
|
|
280
|
+
store.get_key_material(key.key_id, agent_name="intruder")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# Key rotation
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class TestKeyRotation:
|
|
289
|
+
"""Tests for key rotation."""
|
|
290
|
+
|
|
291
|
+
def test_rotate_service_key(self, store: KeyStore) -> None:
|
|
292
|
+
"""Rotating a service key produces a new version."""
|
|
293
|
+
old = store.derive_service_key("rotating-svc")
|
|
294
|
+
new = store.rotate_key(old.key_id, reason="scheduled")
|
|
295
|
+
assert new.version == old.version + 1
|
|
296
|
+
assert new.fingerprint != old.fingerprint
|
|
297
|
+
assert new.status == KeyStatus.ACTIVE
|
|
298
|
+
|
|
299
|
+
def test_old_key_marked_rotated(self, store: KeyStore) -> None:
|
|
300
|
+
"""The old key is marked as ROTATED after rotation."""
|
|
301
|
+
old = store.derive_service_key("rotate-me")
|
|
302
|
+
old_id = old.key_id
|
|
303
|
+
store.rotate_key(old_id)
|
|
304
|
+
|
|
305
|
+
records = store.list_keys(include_inactive=True)
|
|
306
|
+
old_record = next(r for r in records if r.key_id == old_id)
|
|
307
|
+
assert old_record.status == KeyStatus.ROTATED
|
|
308
|
+
assert old_record.rotated_at is not None
|
|
309
|
+
|
|
310
|
+
def test_rotate_team_key_preserves_members(self, store: KeyStore) -> None:
|
|
311
|
+
"""Team key rotation preserves the member list."""
|
|
312
|
+
old = store.create_team_key("team-rotate", members=["opus", "grok"])
|
|
313
|
+
new = store.rotate_key(old.key_id)
|
|
314
|
+
assert new.members == ["opus", "grok"]
|
|
315
|
+
|
|
316
|
+
def test_rotation_log_written(self, store: KeyStore, home: Path) -> None:
|
|
317
|
+
"""Rotation events are logged."""
|
|
318
|
+
old = store.derive_service_key("log-svc")
|
|
319
|
+
store.rotate_key(old.key_id)
|
|
320
|
+
|
|
321
|
+
log_file = home / "security" / "kms" / "rotation-log.json"
|
|
322
|
+
assert log_file.exists()
|
|
323
|
+
log = json.loads(log_file.read_text(encoding="utf-8"))
|
|
324
|
+
assert len(log) >= 1
|
|
325
|
+
assert log[-1]["old_version"] == 1
|
|
326
|
+
assert log[-1]["new_version"] == 2
|
|
327
|
+
|
|
328
|
+
def test_rotate_master_key(self, store: KeyStore) -> None:
|
|
329
|
+
"""Master key can be rotated."""
|
|
330
|
+
keys = store.list_keys(key_type=KeyType.MASTER)
|
|
331
|
+
master = keys[0]
|
|
332
|
+
new = store.rotate_key(master.key_id, reason="security audit")
|
|
333
|
+
assert new.version == master.version + 1
|
|
334
|
+
|
|
335
|
+
def test_rotate_missing_key_raises(self, store: KeyStore) -> None:
|
|
336
|
+
"""Rotating a nonexistent key raises ValueError."""
|
|
337
|
+
with pytest.raises(ValueError, match="not found"):
|
|
338
|
+
store.rotate_key("nonexistent-id")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
# Key revocation
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class TestKeyRevocation:
|
|
347
|
+
"""Tests for key revocation."""
|
|
348
|
+
|
|
349
|
+
def test_revoke_key(self, store: KeyStore) -> None:
|
|
350
|
+
"""Revoking a key marks it and removes material."""
|
|
351
|
+
key = store.derive_service_key("revoke-me")
|
|
352
|
+
revoked = store.revoke_key(key.key_id)
|
|
353
|
+
assert revoked.status == KeyStatus.REVOKED
|
|
354
|
+
|
|
355
|
+
def test_revoked_key_material_deleted(self, store: KeyStore, home: Path) -> None:
|
|
356
|
+
"""Key material is deleted on revocation."""
|
|
357
|
+
key = store.derive_service_key("delete-material")
|
|
358
|
+
key_file = home / "security" / "kms" / "keys" / f"{key.key_id}.key.enc"
|
|
359
|
+
assert key_file.exists()
|
|
360
|
+
store.revoke_key(key.key_id)
|
|
361
|
+
assert not key_file.exists()
|
|
362
|
+
|
|
363
|
+
def test_revoked_key_not_in_active_list(self, store: KeyStore) -> None:
|
|
364
|
+
"""Revoked keys don't appear in active listing."""
|
|
365
|
+
key = store.derive_service_key("hidden")
|
|
366
|
+
store.revoke_key(key.key_id)
|
|
367
|
+
active = store.list_keys()
|
|
368
|
+
assert all(r.key_id != key.key_id for r in active)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
# Listing and status
|
|
373
|
+
# ---------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class TestListingAndStatus:
|
|
377
|
+
"""Tests for key listing and status reporting."""
|
|
378
|
+
|
|
379
|
+
def test_list_keys_by_type(self, store: KeyStore) -> None:
|
|
380
|
+
"""Filter keys by type."""
|
|
381
|
+
store.derive_service_key("svc-1")
|
|
382
|
+
store.create_team_key("team-1")
|
|
383
|
+
services = store.list_keys(key_type=KeyType.SERVICE)
|
|
384
|
+
teams = store.list_keys(key_type=KeyType.TEAM)
|
|
385
|
+
assert all(r.key_type == KeyType.SERVICE for r in services)
|
|
386
|
+
assert all(r.key_type == KeyType.TEAM for r in teams)
|
|
387
|
+
|
|
388
|
+
def test_status_summary(self, store: KeyStore) -> None:
|
|
389
|
+
"""Status returns structured summary."""
|
|
390
|
+
store.derive_service_key("s1")
|
|
391
|
+
store.create_team_key("t1", members=["opus"])
|
|
392
|
+
status = store.status()
|
|
393
|
+
assert status["initialized"] is True
|
|
394
|
+
assert status["active"] >= 3 # master + service + team
|
|
395
|
+
assert "service" in status["by_type"]
|
|
396
|
+
assert "team" in status["by_type"]
|
|
397
|
+
assert "backend_available" in status
|
|
398
|
+
|
|
399
|
+
def test_get_key_material(self, store: KeyStore) -> None:
|
|
400
|
+
"""Raw key material can be retrieved."""
|
|
401
|
+
key = store.derive_service_key("get-material")
|
|
402
|
+
material = store.get_key_material(key.key_id)
|
|
403
|
+
assert len(material) == 32
|
|
404
|
+
|
|
405
|
+
def test_get_key_material_revoked_raises(self, store: KeyStore) -> None:
|
|
406
|
+
"""Cannot get material for revoked key."""
|
|
407
|
+
key = store.derive_service_key("revoked-access")
|
|
408
|
+
store.revoke_key(key.key_id)
|
|
409
|
+
with pytest.raises(ValueError, match="revoked"):
|
|
410
|
+
store.get_key_material(key.key_id)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# ---------------------------------------------------------------------------
|
|
414
|
+
# Backend integration tests
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class TestBackendIntegration:
|
|
419
|
+
"""Tests for sksecurity KMS backend integration."""
|
|
420
|
+
|
|
421
|
+
def test_backend_property_available(self, store: KeyStore) -> None:
|
|
422
|
+
"""Backend property returns KMS when sksecurity is installed."""
|
|
423
|
+
from skcapstone.kms import _HAS_BACKEND
|
|
424
|
+
if _HAS_BACKEND:
|
|
425
|
+
assert store.backend is not None
|
|
426
|
+
assert store.backend.is_unsealed
|
|
427
|
+
else:
|
|
428
|
+
assert store.backend is None
|
|
429
|
+
|
|
430
|
+
def test_status_reports_backend(self, store: KeyStore) -> None:
|
|
431
|
+
"""Status includes backend availability information."""
|
|
432
|
+
status = store.status()
|
|
433
|
+
assert "backend_available" in status
|
|
434
|
+
assert "backend_unsealed" in status
|
|
435
|
+
|
|
436
|
+
def test_backend_4_tier_operations(self, store: KeyStore) -> None:
|
|
437
|
+
"""When backend is available, 4-tier operations work."""
|
|
438
|
+
from skcapstone.kms import _HAS_BACKEND
|
|
439
|
+
if not _HAS_BACKEND:
|
|
440
|
+
pytest.skip("sksecurity not installed")
|
|
441
|
+
|
|
442
|
+
backend = store.backend
|
|
443
|
+
team_key = backend.create_team_key("backend-team")
|
|
444
|
+
assert team_key.team_id == "backend-team"
|
|
445
|
+
|
|
446
|
+
agent_key = backend.create_agent_key("backend-team", "agent-01")
|
|
447
|
+
assert agent_key.agent_id == "agent-01"
|
|
448
|
+
|
|
449
|
+
dek = backend.create_dek("backend-team", "agent-01", purpose="test")
|
|
450
|
+
raw_dek = backend.unwrap_dek(dek.key_id)
|
|
451
|
+
assert len(raw_dek) == 32
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# ---------------------------------------------------------------------------
|
|
455
|
+
# Model tests
|
|
456
|
+
# ---------------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class TestModels:
|
|
460
|
+
"""Tests for Pydantic models."""
|
|
461
|
+
|
|
462
|
+
def test_key_record_defaults(self) -> None:
|
|
463
|
+
"""KeyRecord has sensible defaults."""
|
|
464
|
+
record = KeyRecord(
|
|
465
|
+
key_id="test123",
|
|
466
|
+
key_type=KeyType.SERVICE,
|
|
467
|
+
label="test",
|
|
468
|
+
fingerprint="abc" * 20,
|
|
469
|
+
)
|
|
470
|
+
assert record.status == KeyStatus.ACTIVE
|
|
471
|
+
assert record.version == 1
|
|
472
|
+
assert record.members == []
|
|
473
|
+
|
|
474
|
+
def test_key_record_default_algorithm(self) -> None:
|
|
475
|
+
"""KeyRecord default algorithm is AES-256-GCM based."""
|
|
476
|
+
record = KeyRecord(
|
|
477
|
+
key_id="test",
|
|
478
|
+
key_type=KeyType.SERVICE,
|
|
479
|
+
label="test",
|
|
480
|
+
fingerprint="abc",
|
|
481
|
+
)
|
|
482
|
+
assert "AES-256-GCM" in record.algorithm
|
|
483
|
+
|
|
484
|
+
def test_rotation_entry_serializes(self) -> None:
|
|
485
|
+
"""RotationEntry can be serialized to JSON."""
|
|
486
|
+
entry = RotationEntry(
|
|
487
|
+
key_id="k1",
|
|
488
|
+
old_fingerprint="old",
|
|
489
|
+
new_fingerprint="new",
|
|
490
|
+
old_version=1,
|
|
491
|
+
new_version=2,
|
|
492
|
+
)
|
|
493
|
+
data = entry.model_dump(mode="json")
|
|
494
|
+
assert data["old_version"] == 1
|
|
495
|
+
assert data["new_version"] == 2
|