@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
package/tests/test_pillars.py
CHANGED
|
@@ -6,7 +6,12 @@ import json
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from skcapstone.pillars.identity import generate_identity
|
|
9
|
-
from skcapstone.pillars.security import
|
|
9
|
+
from skcapstone.pillars.security import (
|
|
10
|
+
AuditEntry,
|
|
11
|
+
audit_event,
|
|
12
|
+
initialize_security,
|
|
13
|
+
read_audit_log,
|
|
14
|
+
)
|
|
10
15
|
from skcapstone.pillars.trust import initialize_trust, record_trust_state
|
|
11
16
|
from skcapstone.models import PillarStatus
|
|
12
17
|
|
|
@@ -64,20 +69,50 @@ class TestSecurityPillar:
|
|
|
64
69
|
"""Tests for security initialization and audit logging."""
|
|
65
70
|
|
|
66
71
|
def test_initialize_creates_audit_log(self, tmp_agent_home: Path):
|
|
67
|
-
"""initialize_security should create
|
|
72
|
+
"""initialize_security should create a structured JSONL audit log."""
|
|
68
73
|
initialize_security(tmp_agent_home)
|
|
69
74
|
audit_log = tmp_agent_home / "security" / "audit.log"
|
|
70
75
|
assert audit_log.exists()
|
|
71
|
-
assert "INIT" in audit_log.read_text()
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
line = audit_log.read_text().strip()
|
|
78
|
+
data = json.loads(line)
|
|
79
|
+
assert data["event_type"] == "INIT"
|
|
80
|
+
assert "timestamp" in data
|
|
81
|
+
assert "host" in data
|
|
82
|
+
|
|
83
|
+
def test_audit_event_appends_structured(self, tmp_agent_home: Path):
|
|
84
|
+
"""audit_event should append structured JSON entries."""
|
|
75
85
|
initialize_security(tmp_agent_home)
|
|
76
|
-
audit_event(tmp_agent_home, "TEST", "unit test event")
|
|
86
|
+
entry = audit_event(tmp_agent_home, "TEST", "unit test event")
|
|
87
|
+
|
|
88
|
+
assert isinstance(entry, AuditEntry)
|
|
89
|
+
assert entry.event_type == "TEST"
|
|
90
|
+
assert entry.detail == "unit test event"
|
|
91
|
+
|
|
92
|
+
lines = (tmp_agent_home / "security" / "audit.log").read_text().splitlines()
|
|
93
|
+
assert len(lines) == 2
|
|
94
|
+
parsed = json.loads(lines[1])
|
|
95
|
+
assert parsed["event_type"] == "TEST"
|
|
96
|
+
assert parsed["detail"] == "unit test event"
|
|
97
|
+
|
|
98
|
+
def test_audit_event_with_metadata(self, tmp_agent_home: Path):
|
|
99
|
+
"""audit_event should store optional agent and metadata fields."""
|
|
100
|
+
initialize_security(tmp_agent_home)
|
|
101
|
+
entry = audit_event(
|
|
102
|
+
tmp_agent_home,
|
|
103
|
+
"TOKEN_ISSUE",
|
|
104
|
+
"Issued token abc123",
|
|
105
|
+
agent="opus",
|
|
106
|
+
metadata={"token_id": "abc123", "capabilities": ["read"]},
|
|
107
|
+
)
|
|
77
108
|
|
|
78
|
-
|
|
79
|
-
assert "
|
|
80
|
-
|
|
109
|
+
assert entry.agent == "opus"
|
|
110
|
+
assert entry.metadata["token_id"] == "abc123"
|
|
111
|
+
|
|
112
|
+
lines = (tmp_agent_home / "security" / "audit.log").read_text().splitlines()
|
|
113
|
+
parsed = json.loads(lines[-1])
|
|
114
|
+
assert parsed["agent"] == "opus"
|
|
115
|
+
assert parsed["metadata"]["token_id"] == "abc123"
|
|
81
116
|
|
|
82
117
|
def test_audit_event_creates_dir_if_missing(self, tmp_path: Path):
|
|
83
118
|
"""audit_event should create security dir if it doesn't exist."""
|
|
@@ -85,3 +120,46 @@ class TestSecurityPillar:
|
|
|
85
120
|
fresh_home.mkdir()
|
|
86
121
|
audit_event(fresh_home, "BOOT", "first event")
|
|
87
122
|
assert (fresh_home / "security" / "audit.log").exists()
|
|
123
|
+
|
|
124
|
+
def test_read_audit_log_parses_entries(self, tmp_agent_home: Path):
|
|
125
|
+
"""read_audit_log should return structured AuditEntry objects."""
|
|
126
|
+
initialize_security(tmp_agent_home)
|
|
127
|
+
audit_event(tmp_agent_home, "AUTH", "key verified")
|
|
128
|
+
audit_event(tmp_agent_home, "SYNC_PUSH", "seed pushed")
|
|
129
|
+
|
|
130
|
+
entries = read_audit_log(tmp_agent_home)
|
|
131
|
+
assert len(entries) == 3
|
|
132
|
+
assert entries[0].event_type == "INIT"
|
|
133
|
+
assert entries[1].event_type == "AUTH"
|
|
134
|
+
assert entries[2].event_type == "SYNC_PUSH"
|
|
135
|
+
|
|
136
|
+
def test_read_audit_log_handles_legacy(self, tmp_agent_home: Path):
|
|
137
|
+
"""read_audit_log should gracefully handle old plain-text entries."""
|
|
138
|
+
security_dir = tmp_agent_home / "security"
|
|
139
|
+
security_dir.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
log = security_dir / "audit.log"
|
|
141
|
+
log.write_text(
|
|
142
|
+
"[2026-02-22T12:00:00+00:00] INIT — old format\n"
|
|
143
|
+
"[2026-02-22T12:01:00+00:00] AUTH — legacy auth\n"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
entries = read_audit_log(tmp_agent_home)
|
|
147
|
+
assert len(entries) == 2
|
|
148
|
+
assert all(e.event_type == "LEGACY" for e in entries)
|
|
149
|
+
assert "old format" in entries[0].detail
|
|
150
|
+
|
|
151
|
+
def test_read_audit_log_with_limit(self, tmp_agent_home: Path):
|
|
152
|
+
"""read_audit_log with limit returns only the newest N entries."""
|
|
153
|
+
initialize_security(tmp_agent_home)
|
|
154
|
+
for i in range(5):
|
|
155
|
+
audit_event(tmp_agent_home, "EVENT", f"entry {i}")
|
|
156
|
+
|
|
157
|
+
entries = read_audit_log(tmp_agent_home, limit=2)
|
|
158
|
+
assert len(entries) == 2
|
|
159
|
+
assert "entry 3" in entries[0].detail
|
|
160
|
+
assert "entry 4" in entries[1].detail
|
|
161
|
+
|
|
162
|
+
def test_read_audit_log_empty(self, tmp_path: Path):
|
|
163
|
+
"""read_audit_log returns empty list when no log exists."""
|
|
164
|
+
entries = read_audit_log(tmp_path / "nonexistent")
|
|
165
|
+
assert entries == []
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""Tests for preflight system checks and auto-install."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import platform
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import patch, MagicMock, mock_open
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from skcapstone.preflight import (
|
|
13
|
+
GIT_DOWNLOAD_DEFAULT,
|
|
14
|
+
GIT_DOWNLOAD_URLS,
|
|
15
|
+
GitPreflightResult,
|
|
16
|
+
PreflightResult,
|
|
17
|
+
ToolCheck,
|
|
18
|
+
ToolStatus,
|
|
19
|
+
auto_install_tool,
|
|
20
|
+
check_git,
|
|
21
|
+
check_gpg,
|
|
22
|
+
check_python,
|
|
23
|
+
check_syncthing,
|
|
24
|
+
git_install_hint_for_doctor,
|
|
25
|
+
run_preflight,
|
|
26
|
+
CheckResult,
|
|
27
|
+
PreflightChecker,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestCheckPython:
|
|
32
|
+
"""Tests for check_python()."""
|
|
33
|
+
|
|
34
|
+
def test_returns_toolcheck(self) -> None:
|
|
35
|
+
"""Returns a ToolCheck with installed status."""
|
|
36
|
+
result = check_python()
|
|
37
|
+
assert isinstance(result, ToolCheck)
|
|
38
|
+
assert result.name == "Python"
|
|
39
|
+
assert result.installed is True
|
|
40
|
+
assert result.required is True
|
|
41
|
+
|
|
42
|
+
def test_has_version(self) -> None:
|
|
43
|
+
"""Version string contains major.minor."""
|
|
44
|
+
result = check_python()
|
|
45
|
+
assert "3." in result.version
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestCheckGpg:
|
|
49
|
+
"""Tests for check_gpg()."""
|
|
50
|
+
|
|
51
|
+
def test_returns_toolcheck(self) -> None:
|
|
52
|
+
"""Returns a ToolCheck."""
|
|
53
|
+
result = check_gpg()
|
|
54
|
+
assert isinstance(result, ToolCheck)
|
|
55
|
+
assert result.name == "GnuPG"
|
|
56
|
+
assert result.required is True
|
|
57
|
+
|
|
58
|
+
@patch("skcapstone.preflight.shutil.which", return_value=None)
|
|
59
|
+
def test_missing_has_install_info(self, mock_which: MagicMock) -> None:
|
|
60
|
+
"""When missing, provides install command and download URL."""
|
|
61
|
+
result = check_gpg()
|
|
62
|
+
assert result.status == ToolStatus.MISSING
|
|
63
|
+
assert result.download_url != ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestCheckGit:
|
|
67
|
+
"""Tests for check_git()."""
|
|
68
|
+
|
|
69
|
+
def test_returns_toolcheck(self) -> None:
|
|
70
|
+
"""Returns a ToolCheck."""
|
|
71
|
+
result = check_git()
|
|
72
|
+
assert isinstance(result, ToolCheck)
|
|
73
|
+
assert result.name == "Git"
|
|
74
|
+
|
|
75
|
+
def test_not_required_by_default(self) -> None:
|
|
76
|
+
"""Git is not required by default."""
|
|
77
|
+
result = check_git(required=False)
|
|
78
|
+
assert result.required is False
|
|
79
|
+
|
|
80
|
+
def test_required_when_specified(self) -> None:
|
|
81
|
+
"""Git is required when flag is True."""
|
|
82
|
+
result = check_git(required=True)
|
|
83
|
+
assert result.required is True
|
|
84
|
+
|
|
85
|
+
@patch("skcapstone.preflight.shutil.which", return_value=None)
|
|
86
|
+
def test_missing_has_install_cmd(self, mock_which: MagicMock) -> None:
|
|
87
|
+
"""When missing, provides platform-specific install command."""
|
|
88
|
+
result = check_git(required=True)
|
|
89
|
+
assert result.status == ToolStatus.MISSING
|
|
90
|
+
assert result.install_cmd != "" or result.download_url != ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestCheckSyncthing:
|
|
94
|
+
"""Tests for check_syncthing()."""
|
|
95
|
+
|
|
96
|
+
def test_returns_toolcheck(self) -> None:
|
|
97
|
+
"""Returns a ToolCheck."""
|
|
98
|
+
result = check_syncthing()
|
|
99
|
+
assert isinstance(result, ToolCheck)
|
|
100
|
+
assert result.name == "Syncthing"
|
|
101
|
+
|
|
102
|
+
@patch("skcapstone.preflight.shutil.which", return_value=None)
|
|
103
|
+
def test_missing_has_download_url(self, mock_which: MagicMock) -> None:
|
|
104
|
+
"""When missing, provides download URL."""
|
|
105
|
+
result = check_syncthing()
|
|
106
|
+
assert result.status == ToolStatus.MISSING
|
|
107
|
+
assert "syncthing.net" in result.download_url
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestRunPreflight:
|
|
111
|
+
"""Tests for run_preflight()."""
|
|
112
|
+
|
|
113
|
+
def test_returns_preflight_result(self) -> None:
|
|
114
|
+
"""Returns a PreflightResult with all checks."""
|
|
115
|
+
result = run_preflight()
|
|
116
|
+
assert isinstance(result, PreflightResult)
|
|
117
|
+
assert isinstance(result.python, ToolCheck)
|
|
118
|
+
assert isinstance(result.gpg, ToolCheck)
|
|
119
|
+
assert isinstance(result.git, ToolCheck)
|
|
120
|
+
assert isinstance(result.syncthing, ToolCheck)
|
|
121
|
+
|
|
122
|
+
def test_all_ok_when_optional_missing(self) -> None:
|
|
123
|
+
"""all_ok is True when only optional tools are missing."""
|
|
124
|
+
result = run_preflight(require_git=False, require_syncthing=False)
|
|
125
|
+
if result.python.installed and result.gpg.installed:
|
|
126
|
+
assert result.all_ok is True
|
|
127
|
+
|
|
128
|
+
def test_required_missing_list(self) -> None:
|
|
129
|
+
"""required_missing lists only required missing tools."""
|
|
130
|
+
result = run_preflight()
|
|
131
|
+
for check in result.required_missing:
|
|
132
|
+
assert check.required is True
|
|
133
|
+
assert check.installed is False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TestToolCheck:
|
|
137
|
+
"""Tests for ToolCheck properties."""
|
|
138
|
+
|
|
139
|
+
def test_installed_property(self) -> None:
|
|
140
|
+
"""installed is True when status is INSTALLED."""
|
|
141
|
+
check = ToolCheck(name="Test", status=ToolStatus.INSTALLED, required=True)
|
|
142
|
+
assert check.installed is True
|
|
143
|
+
assert check.ok is True
|
|
144
|
+
|
|
145
|
+
def test_missing_required(self) -> None:
|
|
146
|
+
"""ok is False when required and missing."""
|
|
147
|
+
check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=True)
|
|
148
|
+
assert check.installed is False
|
|
149
|
+
assert check.ok is False
|
|
150
|
+
|
|
151
|
+
def test_missing_optional(self) -> None:
|
|
152
|
+
"""ok is True when optional and missing."""
|
|
153
|
+
check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=False)
|
|
154
|
+
assert check.installed is False
|
|
155
|
+
assert check.ok is True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestAutoInstallTool:
|
|
159
|
+
"""Tests for auto_install_tool()."""
|
|
160
|
+
|
|
161
|
+
def test_already_installed(self) -> None:
|
|
162
|
+
"""Returns True if tool is already installed."""
|
|
163
|
+
check = ToolCheck(name="Test", status=ToolStatus.INSTALLED, required=True)
|
|
164
|
+
assert auto_install_tool(check) is True
|
|
165
|
+
|
|
166
|
+
def test_no_install_cmd(self) -> None:
|
|
167
|
+
"""Returns False if no install command available."""
|
|
168
|
+
check = ToolCheck(name="Test", status=ToolStatus.MISSING, required=True, install_cmd="")
|
|
169
|
+
assert auto_install_tool(check) is False
|
|
170
|
+
|
|
171
|
+
@patch("skcapstone.preflight.subprocess.run")
|
|
172
|
+
def test_runs_install_cmd(self, mock_run: MagicMock) -> None:
|
|
173
|
+
"""Runs the install command when provided."""
|
|
174
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
175
|
+
check = ToolCheck(
|
|
176
|
+
name="Test", status=ToolStatus.MISSING, required=True,
|
|
177
|
+
install_cmd="sudo apt install -y test-tool",
|
|
178
|
+
)
|
|
179
|
+
result = auto_install_tool(check)
|
|
180
|
+
assert result is True
|
|
181
|
+
mock_run.assert_called_once()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# Legacy compatibility
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
class TestGitDownloadUrls:
|
|
189
|
+
"""Tests for legacy platform download URL mapping."""
|
|
190
|
+
|
|
191
|
+
def test_windows_url(self) -> None:
|
|
192
|
+
assert "Windows" in GIT_DOWNLOAD_URLS
|
|
193
|
+
assert "git-scm.com" in GIT_DOWNLOAD_URLS["Windows"]
|
|
194
|
+
|
|
195
|
+
def test_linux_url(self) -> None:
|
|
196
|
+
assert "Linux" in GIT_DOWNLOAD_URLS
|
|
197
|
+
assert "git-scm.com" in GIT_DOWNLOAD_URLS["Linux"]
|
|
198
|
+
|
|
199
|
+
def test_darwin_url(self) -> None:
|
|
200
|
+
assert "Darwin" in GIT_DOWNLOAD_URLS
|
|
201
|
+
assert "git-scm.com" in GIT_DOWNLOAD_URLS["Darwin"]
|
|
202
|
+
|
|
203
|
+
def test_default_url_exists(self) -> None:
|
|
204
|
+
assert "git-scm.com" in GIT_DOWNLOAD_DEFAULT
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestGitPreflightResult:
|
|
208
|
+
"""Tests for legacy GitPreflightResult."""
|
|
209
|
+
|
|
210
|
+
def test_run_returns_result(self) -> None:
|
|
211
|
+
r = GitPreflightResult.run()
|
|
212
|
+
assert isinstance(r.installed, bool)
|
|
213
|
+
assert isinstance(r.platform_label, str)
|
|
214
|
+
assert isinstance(r.message, str)
|
|
215
|
+
assert isinstance(r.download_url, str)
|
|
216
|
+
|
|
217
|
+
@patch("skcapstone.preflight.shutil.which", return_value=None)
|
|
218
|
+
def test_not_installed_has_url(self, mock_which: MagicMock) -> None:
|
|
219
|
+
"""When git is missing, download_url is populated."""
|
|
220
|
+
r = GitPreflightResult.run()
|
|
221
|
+
assert r.installed is False
|
|
222
|
+
assert "git-scm.com" in r.download_url
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TestGitInstallHintForDoctor:
|
|
226
|
+
"""Tests for legacy git_install_hint_for_doctor()."""
|
|
227
|
+
|
|
228
|
+
def test_returns_string(self) -> None:
|
|
229
|
+
hint = git_install_hint_for_doctor()
|
|
230
|
+
assert isinstance(hint, str)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# PreflightChecker tests
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
class TestCheckResult:
|
|
238
|
+
"""Tests for CheckResult dataclass."""
|
|
239
|
+
|
|
240
|
+
def test_ok_status(self) -> None:
|
|
241
|
+
r = CheckResult("test", "ok", "all good")
|
|
242
|
+
assert r.ok is True
|
|
243
|
+
assert r.failed is False
|
|
244
|
+
assert r.warned is False
|
|
245
|
+
|
|
246
|
+
def test_warn_status(self) -> None:
|
|
247
|
+
r = CheckResult("test", "warn", "needs attention", critical=False)
|
|
248
|
+
assert r.ok is False
|
|
249
|
+
assert r.warned is True
|
|
250
|
+
assert r.failed is False
|
|
251
|
+
|
|
252
|
+
def test_fail_status(self) -> None:
|
|
253
|
+
r = CheckResult("test", "fail", "broken")
|
|
254
|
+
assert r.failed is True
|
|
255
|
+
assert r.ok is False
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class TestPreflightCheckerPython:
|
|
259
|
+
"""Tests for PreflightChecker.check_python()."""
|
|
260
|
+
|
|
261
|
+
def test_current_python_passes(self, tmp_path: Path) -> None:
|
|
262
|
+
"""Running Python should be >= 3.11, so check passes."""
|
|
263
|
+
checker = PreflightChecker(home=tmp_path)
|
|
264
|
+
result = checker.check_python()
|
|
265
|
+
assert isinstance(result, CheckResult)
|
|
266
|
+
assert result.name == "python"
|
|
267
|
+
# CI may run older Python; just verify shape
|
|
268
|
+
assert result.status in ("ok", "fail")
|
|
269
|
+
|
|
270
|
+
def test_old_python_fails(self, tmp_path: Path) -> None:
|
|
271
|
+
from collections import namedtuple
|
|
272
|
+
VI = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])
|
|
273
|
+
vi = VI(3, 10, 0, "final", 0)
|
|
274
|
+
checker = PreflightChecker(home=tmp_path)
|
|
275
|
+
with patch("skcapstone.preflight.sys.version_info", vi):
|
|
276
|
+
result = checker.check_python()
|
|
277
|
+
assert result.status == "fail"
|
|
278
|
+
assert result.critical is True
|
|
279
|
+
|
|
280
|
+
def test_311_passes(self, tmp_path: Path) -> None:
|
|
281
|
+
from collections import namedtuple
|
|
282
|
+
VI = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])
|
|
283
|
+
vi = VI(3, 11, 0, "final", 0)
|
|
284
|
+
checker = PreflightChecker(home=tmp_path)
|
|
285
|
+
with patch("skcapstone.preflight.sys.version_info", vi):
|
|
286
|
+
result = checker.check_python()
|
|
287
|
+
assert result.status == "ok"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class TestPreflightCheckerPackages:
|
|
291
|
+
"""Tests for PreflightChecker.check_packages()."""
|
|
292
|
+
|
|
293
|
+
def test_returns_check_result(self, tmp_path: Path) -> None:
|
|
294
|
+
checker = PreflightChecker(home=tmp_path)
|
|
295
|
+
result = checker.check_packages()
|
|
296
|
+
assert isinstance(result, CheckResult)
|
|
297
|
+
assert result.name == "packages"
|
|
298
|
+
|
|
299
|
+
def test_missing_package_fails(self, tmp_path: Path) -> None:
|
|
300
|
+
import builtins
|
|
301
|
+
real_import = builtins.__import__
|
|
302
|
+
|
|
303
|
+
def _mock_import(name, *args, **kwargs):
|
|
304
|
+
if name == "skcomm":
|
|
305
|
+
raise ImportError("no module named skcomm")
|
|
306
|
+
return real_import(name, *args, **kwargs)
|
|
307
|
+
|
|
308
|
+
checker = PreflightChecker(home=tmp_path)
|
|
309
|
+
with patch("builtins.__import__", side_effect=_mock_import):
|
|
310
|
+
result = checker.check_packages()
|
|
311
|
+
assert result.status == "fail"
|
|
312
|
+
assert "skcomm" in result.message
|
|
313
|
+
|
|
314
|
+
def test_all_present_ok(self, tmp_path: Path) -> None:
|
|
315
|
+
checker = PreflightChecker(home=tmp_path)
|
|
316
|
+
with patch("builtins.__import__", return_value=MagicMock()):
|
|
317
|
+
result = checker.check_packages()
|
|
318
|
+
assert result.status == "ok"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class TestPreflightCheckerOllama:
|
|
322
|
+
"""Tests for PreflightChecker.check_ollama()."""
|
|
323
|
+
|
|
324
|
+
def test_ollama_unreachable_warns(self, tmp_path: Path) -> None:
|
|
325
|
+
"""If Ollama is not running, result is warn (non-critical)."""
|
|
326
|
+
checker = PreflightChecker(home=tmp_path)
|
|
327
|
+
with patch("urllib.request.urlopen", side_effect=OSError("refused")):
|
|
328
|
+
result = checker.check_ollama()
|
|
329
|
+
assert result.status == "warn"
|
|
330
|
+
assert result.critical is False
|
|
331
|
+
|
|
332
|
+
def test_ollama_running_with_models(self, tmp_path: Path) -> None:
|
|
333
|
+
checker = PreflightChecker(home=tmp_path)
|
|
334
|
+
mock_resp = MagicMock()
|
|
335
|
+
mock_resp.read.return_value = json.dumps(
|
|
336
|
+
{"models": [{"name": "llama3.2"}, {"name": "mistral"}]}
|
|
337
|
+
).encode()
|
|
338
|
+
mock_resp.__enter__ = lambda s: s
|
|
339
|
+
mock_resp.__exit__ = MagicMock(return_value=False)
|
|
340
|
+
with patch("urllib.request.urlopen", return_value=mock_resp):
|
|
341
|
+
result = checker.check_ollama()
|
|
342
|
+
assert result.status == "ok"
|
|
343
|
+
assert "llama3.2" in result.message
|
|
344
|
+
|
|
345
|
+
def test_ollama_running_no_models_warns(self, tmp_path: Path) -> None:
|
|
346
|
+
checker = PreflightChecker(home=tmp_path)
|
|
347
|
+
mock_resp = MagicMock()
|
|
348
|
+
mock_resp.read.return_value = json.dumps({"models": []}).encode()
|
|
349
|
+
mock_resp.__enter__ = lambda s: s
|
|
350
|
+
mock_resp.__exit__ = MagicMock(return_value=False)
|
|
351
|
+
with patch("urllib.request.urlopen", return_value=mock_resp):
|
|
352
|
+
result = checker.check_ollama()
|
|
353
|
+
assert result.status == "warn"
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestPreflightCheckerIdentity:
|
|
357
|
+
"""Tests for PreflightChecker.check_identity()."""
|
|
358
|
+
|
|
359
|
+
def test_identity_json_found(self, tmp_path: Path) -> None:
|
|
360
|
+
identity_dir = tmp_path / "identity"
|
|
361
|
+
identity_dir.mkdir()
|
|
362
|
+
(identity_dir / "identity.json").write_text(
|
|
363
|
+
json.dumps({"name": "opus", "fingerprint": "ABCD1234EFGH5678"}),
|
|
364
|
+
encoding="utf-8",
|
|
365
|
+
)
|
|
366
|
+
checker = PreflightChecker(home=tmp_path)
|
|
367
|
+
result = checker.check_identity()
|
|
368
|
+
assert result.status == "ok"
|
|
369
|
+
assert "opus" in result.message
|
|
370
|
+
|
|
371
|
+
def test_no_identity_fails(self, tmp_path: Path) -> None:
|
|
372
|
+
checker = PreflightChecker(home=tmp_path)
|
|
373
|
+
result = checker.check_identity()
|
|
374
|
+
assert result.status == "fail"
|
|
375
|
+
assert result.critical is True
|
|
376
|
+
|
|
377
|
+
def test_manifest_only_warns(self, tmp_path: Path) -> None:
|
|
378
|
+
(tmp_path / "manifest.json").write_text("{}", encoding="utf-8")
|
|
379
|
+
checker = PreflightChecker(home=tmp_path)
|
|
380
|
+
result = checker.check_identity()
|
|
381
|
+
assert result.status == "warn"
|
|
382
|
+
assert result.critical is False
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class TestPreflightCheckerHomeDirs:
|
|
386
|
+
"""Tests for PreflightChecker.check_home_dirs()."""
|
|
387
|
+
|
|
388
|
+
def test_all_dirs_present(self, tmp_path: Path) -> None:
|
|
389
|
+
for d in ("memory", "trust", "identity", "config"):
|
|
390
|
+
(tmp_path / d).mkdir()
|
|
391
|
+
checker = PreflightChecker(home=tmp_path)
|
|
392
|
+
result = checker.check_home_dirs()
|
|
393
|
+
assert result.status == "ok"
|
|
394
|
+
|
|
395
|
+
def test_missing_dirs_fail(self, tmp_path: Path) -> None:
|
|
396
|
+
checker = PreflightChecker(home=tmp_path)
|
|
397
|
+
result = checker.check_home_dirs()
|
|
398
|
+
assert result.status == "fail"
|
|
399
|
+
assert "memory" in result.message or "trust" in result.message
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class TestPreflightCheckerConfig:
|
|
403
|
+
"""Tests for PreflightChecker.check_config()."""
|
|
404
|
+
|
|
405
|
+
def test_no_config_warns(self, tmp_path: Path) -> None:
|
|
406
|
+
checker = PreflightChecker(home=tmp_path)
|
|
407
|
+
result = checker.check_config()
|
|
408
|
+
assert result.status == "warn"
|
|
409
|
+
assert result.critical is False
|
|
410
|
+
|
|
411
|
+
def test_valid_config_ok(self, tmp_path: Path) -> None:
|
|
412
|
+
cfg_dir = tmp_path / "config"
|
|
413
|
+
cfg_dir.mkdir()
|
|
414
|
+
(cfg_dir / "consciousness.yaml").write_text(
|
|
415
|
+
"enabled: true\n", encoding="utf-8"
|
|
416
|
+
)
|
|
417
|
+
checker = PreflightChecker(home=tmp_path)
|
|
418
|
+
result = checker.check_config()
|
|
419
|
+
assert result.status == "ok"
|
|
420
|
+
|
|
421
|
+
def test_invalid_yaml_fails(self, tmp_path: Path) -> None:
|
|
422
|
+
cfg_dir = tmp_path / "config"
|
|
423
|
+
cfg_dir.mkdir()
|
|
424
|
+
(cfg_dir / "consciousness.yaml").write_text(
|
|
425
|
+
"enabled: [\nbad yaml", encoding="utf-8"
|
|
426
|
+
)
|
|
427
|
+
checker = PreflightChecker(home=tmp_path)
|
|
428
|
+
result = checker.check_config()
|
|
429
|
+
assert result.status == "fail"
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class TestPreflightCheckerDiskSpace:
|
|
433
|
+
"""Tests for PreflightChecker.check_disk_space()."""
|
|
434
|
+
|
|
435
|
+
def test_plenty_of_space_ok(self, tmp_path: Path) -> None:
|
|
436
|
+
import shutil
|
|
437
|
+
mock_usage = shutil.disk_usage.__class__
|
|
438
|
+
# Return 100 GB free
|
|
439
|
+
with patch("shutil.disk_usage") as mock_du:
|
|
440
|
+
mock_du.return_value = MagicMock(free=100 * 1024 ** 3)
|
|
441
|
+
checker = PreflightChecker(home=tmp_path)
|
|
442
|
+
result = checker.check_disk_space()
|
|
443
|
+
assert result.status == "ok"
|
|
444
|
+
|
|
445
|
+
def test_low_disk_warns(self, tmp_path: Path) -> None:
|
|
446
|
+
with patch("shutil.disk_usage") as mock_du:
|
|
447
|
+
mock_du.return_value = MagicMock(free=2 * 1024 ** 3) # 2 GB
|
|
448
|
+
checker = PreflightChecker(home=tmp_path)
|
|
449
|
+
result = checker.check_disk_space()
|
|
450
|
+
assert result.status == "warn"
|
|
451
|
+
assert result.critical is False
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class TestPreflightCheckerRunAll:
|
|
455
|
+
"""Tests for PreflightChecker.run_all()."""
|
|
456
|
+
|
|
457
|
+
def test_returns_summary_dict(self, tmp_path: Path) -> None:
|
|
458
|
+
checker = PreflightChecker(home=tmp_path)
|
|
459
|
+
summary = checker.run_all()
|
|
460
|
+
assert isinstance(summary, dict)
|
|
461
|
+
assert "ok" in summary
|
|
462
|
+
assert "checks" in summary
|
|
463
|
+
assert "warnings" in summary
|
|
464
|
+
assert "failures" in summary
|
|
465
|
+
assert "critical_failures" in summary
|
|
466
|
+
|
|
467
|
+
def test_all_checks_present(self, tmp_path: Path) -> None:
|
|
468
|
+
checker = PreflightChecker(home=tmp_path)
|
|
469
|
+
summary = checker.run_all()
|
|
470
|
+
names = {c["name"] for c in summary["checks"]}
|
|
471
|
+
assert names == {
|
|
472
|
+
"python", "packages", "ollama", "identity",
|
|
473
|
+
"home_dirs", "config", "disk_space",
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
def test_ok_false_on_critical_failure(self, tmp_path: Path) -> None:
|
|
477
|
+
"""If home dirs missing and no identity, ok should be False."""
|
|
478
|
+
checker = PreflightChecker(home=tmp_path)
|
|
479
|
+
# No identity, no dirs — critical failures expected
|
|
480
|
+
summary = checker.run_all()
|
|
481
|
+
assert isinstance(summary["ok"], bool)
|
|
482
|
+
# At minimum, critical_failures + failures are ints
|
|
483
|
+
assert isinstance(summary["critical_failures"], int)
|
|
484
|
+
assert isinstance(summary["failures"], int)
|