@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,482 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for skcapstone SyncEngine.
|
|
3
|
+
|
|
4
|
+
Covers orchestration of push/pull across backends, config/state
|
|
5
|
+
persistence, backend filtering, and the init_sync factory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from unittest.mock import MagicMock, patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Fixtures
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def agent_home(tmp_path: Path) -> Path:
|
|
25
|
+
home = tmp_path / ".skcapstone"
|
|
26
|
+
home.mkdir()
|
|
27
|
+
for d in ("identity", "memory", "trust", "config", "skills"):
|
|
28
|
+
(home / d).mkdir()
|
|
29
|
+
|
|
30
|
+
(home / "identity" / "identity.json").write_text(
|
|
31
|
+
json.dumps({
|
|
32
|
+
"name": "EngineTestAgent",
|
|
33
|
+
"fingerprint": "FFFF1111AAAA2222BBBB3333CCCC4444DDDD5555",
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
(home / "trust" / "trust.json").write_text(
|
|
37
|
+
json.dumps({"depth": 3.0, "trust_level": 0.7})
|
|
38
|
+
)
|
|
39
|
+
(home / "config" / "config.yaml").write_text("agent_name: EngineTestAgent\n")
|
|
40
|
+
(home / "manifest.json").write_text(
|
|
41
|
+
json.dumps({"name": "EngineTestAgent", "version": "0.1.0", "connectors": []})
|
|
42
|
+
)
|
|
43
|
+
for layer in ("short-term", "mid-term", "long-term"):
|
|
44
|
+
(home / "memory" / layer).mkdir(parents=True)
|
|
45
|
+
(home / "memory" / "long-term" / "mem.json").write_text(
|
|
46
|
+
json.dumps({"content": "remember this"})
|
|
47
|
+
)
|
|
48
|
+
return home
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def engine(agent_home: Path):
|
|
53
|
+
from skcapstone.sync.engine import SyncEngine
|
|
54
|
+
|
|
55
|
+
e = SyncEngine(agent_home)
|
|
56
|
+
e.config.encrypt = False
|
|
57
|
+
return e
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def local_backend_config(tmp_path: Path):
|
|
62
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
63
|
+
|
|
64
|
+
backup = tmp_path / "local-bkp"
|
|
65
|
+
backup.mkdir()
|
|
66
|
+
return SyncBackendConfig(
|
|
67
|
+
backend_type=SyncBackendType.LOCAL,
|
|
68
|
+
local_path=backup,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Initialisation
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestSyncEngineInit:
|
|
78
|
+
def test_default_config_is_empty(self, engine):
|
|
79
|
+
"""Fresh engine has no backends configured."""
|
|
80
|
+
assert engine.config.backends == []
|
|
81
|
+
|
|
82
|
+
def test_state_starts_at_zero(self, engine):
|
|
83
|
+
"""Fresh engine state has zero push/pull counts."""
|
|
84
|
+
assert engine.state.push_count == 0
|
|
85
|
+
assert engine.state.pull_count == 0
|
|
86
|
+
assert engine.state.last_push is None
|
|
87
|
+
assert engine.state.last_pull is None
|
|
88
|
+
|
|
89
|
+
def test_creates_sync_directory(self, agent_home: Path):
|
|
90
|
+
"""Engine constructor creates agent_home/sync/."""
|
|
91
|
+
from skcapstone.sync.engine import SyncEngine
|
|
92
|
+
|
|
93
|
+
SyncEngine(agent_home)
|
|
94
|
+
assert (agent_home / "sync").is_dir()
|
|
95
|
+
|
|
96
|
+
def test_loads_existing_config(self, agent_home: Path):
|
|
97
|
+
"""Engine reads backends from an existing config.yaml."""
|
|
98
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
99
|
+
|
|
100
|
+
config_data = {
|
|
101
|
+
"backends": [{"backend_type": "syncthing", "enabled": True}],
|
|
102
|
+
"encrypt": False,
|
|
103
|
+
"auto_push": True,
|
|
104
|
+
"auto_pull": True,
|
|
105
|
+
}
|
|
106
|
+
(agent_home / "sync").mkdir(exist_ok=True)
|
|
107
|
+
(agent_home / "sync" / "config.yaml").write_text(
|
|
108
|
+
yaml.dump(config_data, default_flow_style=False)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
from skcapstone.sync.engine import SyncEngine
|
|
112
|
+
|
|
113
|
+
eng = SyncEngine(agent_home)
|
|
114
|
+
assert len(eng.config.backends) == 1
|
|
115
|
+
assert eng.config.backends[0].backend_type == SyncBackendType.SYNCTHING
|
|
116
|
+
|
|
117
|
+
def test_loads_existing_state(self, agent_home: Path):
|
|
118
|
+
"""Engine reads push/pull counts from an existing state.json."""
|
|
119
|
+
(agent_home / "sync").mkdir(exist_ok=True)
|
|
120
|
+
(agent_home / "sync" / "state.json").write_text(
|
|
121
|
+
json.dumps({"push_count": 5, "pull_count": 3})
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
from skcapstone.sync.engine import SyncEngine
|
|
125
|
+
|
|
126
|
+
eng = SyncEngine(agent_home)
|
|
127
|
+
assert eng.state.push_count == 5
|
|
128
|
+
assert eng.state.pull_count == 3
|
|
129
|
+
|
|
130
|
+
def test_corrupt_config_falls_back_to_defaults(self, agent_home: Path):
|
|
131
|
+
"""Corrupt config.yaml results in default SyncConfig."""
|
|
132
|
+
(agent_home / "sync").mkdir(exist_ok=True)
|
|
133
|
+
(agent_home / "sync" / "config.yaml").write_text("{{{NOT YAML")
|
|
134
|
+
|
|
135
|
+
from skcapstone.sync.engine import SyncEngine
|
|
136
|
+
|
|
137
|
+
eng = SyncEngine(agent_home)
|
|
138
|
+
assert eng.config.backends == []
|
|
139
|
+
|
|
140
|
+
def test_corrupt_state_falls_back_to_defaults(self, agent_home: Path):
|
|
141
|
+
"""Corrupt state.json results in default SyncState."""
|
|
142
|
+
(agent_home / "sync").mkdir(exist_ok=True)
|
|
143
|
+
(agent_home / "sync" / "state.json").write_text("NOT JSON")
|
|
144
|
+
|
|
145
|
+
from skcapstone.sync.engine import SyncEngine
|
|
146
|
+
|
|
147
|
+
eng = SyncEngine(agent_home)
|
|
148
|
+
assert eng.state.push_count == 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# add_backend / save_config
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class TestAddBackend:
|
|
157
|
+
def test_add_backend_appends(self, engine, local_backend_config):
|
|
158
|
+
engine.add_backend(local_backend_config)
|
|
159
|
+
assert len(engine.config.backends) == 1
|
|
160
|
+
|
|
161
|
+
def test_add_backend_replaces_same_type(self, engine, tmp_path: Path):
|
|
162
|
+
"""Adding a backend of the same type replaces the existing one."""
|
|
163
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
164
|
+
|
|
165
|
+
first = SyncBackendConfig(
|
|
166
|
+
backend_type=SyncBackendType.LOCAL,
|
|
167
|
+
local_path=tmp_path / "first",
|
|
168
|
+
)
|
|
169
|
+
(tmp_path / "first").mkdir()
|
|
170
|
+
second = SyncBackendConfig(
|
|
171
|
+
backend_type=SyncBackendType.LOCAL,
|
|
172
|
+
local_path=tmp_path / "second",
|
|
173
|
+
)
|
|
174
|
+
(tmp_path / "second").mkdir()
|
|
175
|
+
|
|
176
|
+
engine.add_backend(first)
|
|
177
|
+
engine.add_backend(second)
|
|
178
|
+
# Only one LOCAL backend should remain
|
|
179
|
+
local_backends = [
|
|
180
|
+
b for b in engine.config.backends
|
|
181
|
+
if b.backend_type.value == "local"
|
|
182
|
+
]
|
|
183
|
+
assert len(local_backends) == 1
|
|
184
|
+
assert local_backends[0].local_path == tmp_path / "second"
|
|
185
|
+
|
|
186
|
+
def test_add_backend_persists_to_disk(self, engine, local_backend_config, agent_home: Path):
|
|
187
|
+
engine.add_backend(local_backend_config)
|
|
188
|
+
config_file = agent_home / "sync" / "config.yaml"
|
|
189
|
+
assert config_file.exists()
|
|
190
|
+
data = yaml.safe_load(config_file.read_text())
|
|
191
|
+
assert len(data["backends"]) == 1
|
|
192
|
+
|
|
193
|
+
def test_config_reloaded_by_new_engine(self, agent_home: Path, local_backend_config):
|
|
194
|
+
from skcapstone.sync.engine import SyncEngine
|
|
195
|
+
|
|
196
|
+
eng1 = SyncEngine(agent_home)
|
|
197
|
+
eng1.config.encrypt = False
|
|
198
|
+
eng1.add_backend(local_backend_config)
|
|
199
|
+
|
|
200
|
+
eng2 = SyncEngine(agent_home)
|
|
201
|
+
assert len(eng2.config.backends) == 1
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ---------------------------------------------------------------------------
|
|
205
|
+
# push
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestSyncEnginePush:
|
|
210
|
+
def test_push_local_backend_succeeds(self, engine, local_backend_config):
|
|
211
|
+
engine.add_backend(local_backend_config)
|
|
212
|
+
results = engine.push()
|
|
213
|
+
assert results.get("local") is True
|
|
214
|
+
|
|
215
|
+
def test_push_increments_push_count(self, engine, local_backend_config):
|
|
216
|
+
engine.add_backend(local_backend_config)
|
|
217
|
+
engine.push()
|
|
218
|
+
assert engine.state.push_count == 1
|
|
219
|
+
|
|
220
|
+
def test_push_sets_last_push_backend(self, engine, local_backend_config):
|
|
221
|
+
engine.add_backend(local_backend_config)
|
|
222
|
+
engine.push()
|
|
223
|
+
assert engine.state.last_push_backend == "local"
|
|
224
|
+
|
|
225
|
+
def test_push_sets_last_push_timestamp(self, engine, local_backend_config):
|
|
226
|
+
engine.add_backend(local_backend_config)
|
|
227
|
+
engine.push()
|
|
228
|
+
assert engine.state.last_push is not None
|
|
229
|
+
|
|
230
|
+
def test_push_persists_state(self, engine, agent_home: Path, local_backend_config):
|
|
231
|
+
engine.add_backend(local_backend_config)
|
|
232
|
+
engine.push()
|
|
233
|
+
state_file = agent_home / "sync" / "state.json"
|
|
234
|
+
assert state_file.exists()
|
|
235
|
+
data = json.loads(state_file.read_text())
|
|
236
|
+
assert data["push_count"] == 1
|
|
237
|
+
|
|
238
|
+
def test_push_skips_disabled_backend(self, engine, tmp_path: Path):
|
|
239
|
+
"""Backends with enabled=False should be skipped."""
|
|
240
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
241
|
+
|
|
242
|
+
backup = tmp_path / "disabled-bkp"
|
|
243
|
+
backup.mkdir()
|
|
244
|
+
disabled = SyncBackendConfig(
|
|
245
|
+
backend_type=SyncBackendType.LOCAL,
|
|
246
|
+
local_path=backup,
|
|
247
|
+
enabled=False,
|
|
248
|
+
)
|
|
249
|
+
engine.add_backend(disabled)
|
|
250
|
+
results = engine.push()
|
|
251
|
+
assert "local" not in results
|
|
252
|
+
|
|
253
|
+
def test_push_skips_unavailable_backend(self, engine, agent_home: Path):
|
|
254
|
+
"""Unavailable backends are reported as False in results."""
|
|
255
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
256
|
+
|
|
257
|
+
config = SyncBackendConfig(backend_type=SyncBackendType.SYNCTHING)
|
|
258
|
+
engine.add_backend(config)
|
|
259
|
+
|
|
260
|
+
with patch("shutil.which", return_value=None):
|
|
261
|
+
results = engine.push()
|
|
262
|
+
|
|
263
|
+
assert results.get("syncthing") is False
|
|
264
|
+
|
|
265
|
+
def test_push_with_backend_filter(self, engine, tmp_path: Path):
|
|
266
|
+
"""backend_filter should push only to the named backend."""
|
|
267
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
268
|
+
|
|
269
|
+
backup = tmp_path / "bkp"
|
|
270
|
+
backup.mkdir()
|
|
271
|
+
engine.add_backend(SyncBackendConfig(backend_type=SyncBackendType.SYNCTHING))
|
|
272
|
+
engine.add_backend(SyncBackendConfig(
|
|
273
|
+
backend_type=SyncBackendType.LOCAL, local_path=backup
|
|
274
|
+
))
|
|
275
|
+
|
|
276
|
+
with patch("shutil.which", return_value=None):
|
|
277
|
+
results = engine.push(backend_filter="local")
|
|
278
|
+
|
|
279
|
+
assert "local" in results
|
|
280
|
+
assert "syncthing" not in results
|
|
281
|
+
|
|
282
|
+
def test_push_no_backends_returns_empty(self, engine):
|
|
283
|
+
results = engine.push()
|
|
284
|
+
assert results == {}
|
|
285
|
+
|
|
286
|
+
def test_push_multiple_backends_all_succeed(self, engine, tmp_path: Path):
|
|
287
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
288
|
+
|
|
289
|
+
for i in range(2):
|
|
290
|
+
d = tmp_path / f"bkp{i}"
|
|
291
|
+
d.mkdir()
|
|
292
|
+
engine.add_backend(SyncBackendConfig(
|
|
293
|
+
backend_type=SyncBackendType.LOCAL, local_path=d
|
|
294
|
+
))
|
|
295
|
+
# Second add_backend replaces first (same type), so only 1 local backend
|
|
296
|
+
results = engine.push()
|
|
297
|
+
assert "local" in results
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
# pull
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class TestSyncEnginePull:
|
|
306
|
+
def _push_to_local(self, engine, local_backend_config):
|
|
307
|
+
engine.add_backend(local_backend_config)
|
|
308
|
+
engine.push()
|
|
309
|
+
|
|
310
|
+
def test_pull_returns_none_with_no_backends(self, engine):
|
|
311
|
+
assert engine.pull() is None
|
|
312
|
+
|
|
313
|
+
def test_pull_restores_state(self, engine, local_backend_config, tmp_path: Path):
|
|
314
|
+
from skcapstone.sync.engine import SyncEngine
|
|
315
|
+
|
|
316
|
+
self._push_to_local(engine, local_backend_config)
|
|
317
|
+
|
|
318
|
+
restore_home = tmp_path / "restore"
|
|
319
|
+
restore_home.mkdir()
|
|
320
|
+
engine2 = SyncEngine(restore_home)
|
|
321
|
+
engine2.config.encrypt = False
|
|
322
|
+
engine2.add_backend(local_backend_config)
|
|
323
|
+
result = engine2.pull()
|
|
324
|
+
assert result is not None
|
|
325
|
+
|
|
326
|
+
def test_pull_increments_pull_count(self, engine, local_backend_config, tmp_path: Path):
|
|
327
|
+
from skcapstone.sync.engine import SyncEngine
|
|
328
|
+
|
|
329
|
+
self._push_to_local(engine, local_backend_config)
|
|
330
|
+
|
|
331
|
+
restore = tmp_path / "restore"
|
|
332
|
+
restore.mkdir()
|
|
333
|
+
engine2 = SyncEngine(restore)
|
|
334
|
+
engine2.config.encrypt = False
|
|
335
|
+
engine2.add_backend(local_backend_config)
|
|
336
|
+
engine2.pull()
|
|
337
|
+
assert engine2.state.pull_count == 1
|
|
338
|
+
|
|
339
|
+
def test_pull_sets_last_pull_backend(self, engine, local_backend_config, tmp_path: Path):
|
|
340
|
+
from skcapstone.sync.engine import SyncEngine
|
|
341
|
+
|
|
342
|
+
self._push_to_local(engine, local_backend_config)
|
|
343
|
+
|
|
344
|
+
restore = tmp_path / "restore"
|
|
345
|
+
restore.mkdir()
|
|
346
|
+
engine2 = SyncEngine(restore)
|
|
347
|
+
engine2.config.encrypt = False
|
|
348
|
+
engine2.add_backend(local_backend_config)
|
|
349
|
+
engine2.pull()
|
|
350
|
+
assert engine2.state.last_pull_backend == "local"
|
|
351
|
+
|
|
352
|
+
def test_pull_dry_run_does_not_extract(self, engine, local_backend_config, tmp_path: Path):
|
|
353
|
+
"""dry_run=True downloads the vault but doesn't call unpack."""
|
|
354
|
+
from skcapstone.sync.engine import SyncEngine
|
|
355
|
+
|
|
356
|
+
self._push_to_local(engine, local_backend_config)
|
|
357
|
+
|
|
358
|
+
restore = tmp_path / "restore"
|
|
359
|
+
restore.mkdir()
|
|
360
|
+
engine2 = SyncEngine(restore)
|
|
361
|
+
engine2.config.encrypt = False
|
|
362
|
+
engine2.add_backend(local_backend_config)
|
|
363
|
+
|
|
364
|
+
with patch.object(engine2.vault, "unpack") as mock_unpack:
|
|
365
|
+
result = engine2.pull(dry_run=True)
|
|
366
|
+
|
|
367
|
+
assert result is not None
|
|
368
|
+
mock_unpack.assert_not_called()
|
|
369
|
+
|
|
370
|
+
def test_pull_skips_disabled_backend(self, engine, tmp_path: Path):
|
|
371
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
372
|
+
|
|
373
|
+
backup = tmp_path / "bkp"
|
|
374
|
+
backup.mkdir()
|
|
375
|
+
disabled = SyncBackendConfig(
|
|
376
|
+
backend_type=SyncBackendType.LOCAL,
|
|
377
|
+
local_path=backup,
|
|
378
|
+
enabled=False,
|
|
379
|
+
)
|
|
380
|
+
engine.add_backend(disabled)
|
|
381
|
+
result = engine.pull()
|
|
382
|
+
assert result is None
|
|
383
|
+
|
|
384
|
+
def test_pull_with_backend_filter(self, engine, local_backend_config, tmp_path: Path):
|
|
385
|
+
from skcapstone.sync.engine import SyncEngine
|
|
386
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
387
|
+
|
|
388
|
+
self._push_to_local(engine, local_backend_config)
|
|
389
|
+
|
|
390
|
+
restore = tmp_path / "restore"
|
|
391
|
+
restore.mkdir()
|
|
392
|
+
engine2 = SyncEngine(restore)
|
|
393
|
+
engine2.config.encrypt = False
|
|
394
|
+
engine2.add_backend(local_backend_config)
|
|
395
|
+
# Also add syncthing (unavailable) - filter should pull from local only
|
|
396
|
+
engine2.add_backend(SyncBackendConfig(backend_type=SyncBackendType.SYNCTHING))
|
|
397
|
+
|
|
398
|
+
with patch("shutil.which", side_effect=lambda x: None if x == "syncthing" else "/usr/bin/git"):
|
|
399
|
+
result = engine2.pull(backend_filter="local")
|
|
400
|
+
assert result is not None
|
|
401
|
+
|
|
402
|
+
def test_pull_skips_unavailable_backend_tries_next(self, engine, local_backend_config, tmp_path: Path):
|
|
403
|
+
"""Pull skips unavailable backends and tries the next one."""
|
|
404
|
+
from skcapstone.sync.engine import SyncEngine
|
|
405
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
406
|
+
|
|
407
|
+
self._push_to_local(engine, local_backend_config)
|
|
408
|
+
|
|
409
|
+
restore = tmp_path / "restore"
|
|
410
|
+
restore.mkdir()
|
|
411
|
+
engine2 = SyncEngine(restore)
|
|
412
|
+
engine2.config.encrypt = False
|
|
413
|
+
# Add syncthing first (unavailable), then local
|
|
414
|
+
engine2.config.backends = [
|
|
415
|
+
SyncBackendConfig(backend_type=SyncBackendType.SYNCTHING),
|
|
416
|
+
local_backend_config,
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
with patch("shutil.which", side_effect=lambda x: None if x == "syncthing" else "/bin/true"):
|
|
420
|
+
result = engine2.pull()
|
|
421
|
+
# Local backend should succeed
|
|
422
|
+
assert result is not None
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# ---------------------------------------------------------------------------
|
|
426
|
+
# status
|
|
427
|
+
# ---------------------------------------------------------------------------
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class TestSyncEngineStatus:
|
|
431
|
+
def test_status_keys(self, engine):
|
|
432
|
+
info = engine.status()
|
|
433
|
+
assert "state" in info
|
|
434
|
+
assert "backends" in info
|
|
435
|
+
assert "vaults" in info
|
|
436
|
+
assert "encrypt" in info
|
|
437
|
+
assert "auto_push" in info
|
|
438
|
+
|
|
439
|
+
def test_status_reports_backend_availability(self, engine):
|
|
440
|
+
from skcapstone.sync.models import SyncBackendConfig, SyncBackendType
|
|
441
|
+
|
|
442
|
+
engine.add_backend(SyncBackendConfig(backend_type=SyncBackendType.SYNCTHING))
|
|
443
|
+
with patch("shutil.which", return_value=None):
|
|
444
|
+
info = engine.status()
|
|
445
|
+
assert info["backends"][0]["available"] is False
|
|
446
|
+
|
|
447
|
+
def test_status_vault_count(self, engine, local_backend_config):
|
|
448
|
+
engine.add_backend(local_backend_config)
|
|
449
|
+
engine.push()
|
|
450
|
+
info = engine.status()
|
|
451
|
+
assert info["vaults"] >= 1
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# ---------------------------------------------------------------------------
|
|
455
|
+
# init_sync factory
|
|
456
|
+
# ---------------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class TestInitSync:
|
|
460
|
+
def test_init_sync_returns_engine(self, agent_home: Path):
|
|
461
|
+
from skcapstone.sync.engine import SyncEngine, init_sync
|
|
462
|
+
from skcapstone.sync.models import SyncBackendType
|
|
463
|
+
|
|
464
|
+
eng = init_sync(agent_home, SyncBackendType.SYNCTHING)
|
|
465
|
+
assert isinstance(eng, SyncEngine)
|
|
466
|
+
|
|
467
|
+
def test_init_sync_adds_backend(self, agent_home: Path):
|
|
468
|
+
from skcapstone.sync.engine import init_sync
|
|
469
|
+
from skcapstone.sync.models import SyncBackendType
|
|
470
|
+
|
|
471
|
+
eng = init_sync(agent_home, SyncBackendType.SYNCTHING)
|
|
472
|
+
assert len(eng.config.backends) == 1
|
|
473
|
+
assert eng.config.backends[0].backend_type == SyncBackendType.SYNCTHING
|
|
474
|
+
|
|
475
|
+
def test_init_sync_with_local_backend(self, agent_home: Path, tmp_path: Path):
|
|
476
|
+
from skcapstone.sync.engine import init_sync
|
|
477
|
+
from skcapstone.sync.models import SyncBackendType
|
|
478
|
+
|
|
479
|
+
backup = tmp_path / "bkp"
|
|
480
|
+
backup.mkdir()
|
|
481
|
+
eng = init_sync(agent_home, SyncBackendType.LOCAL, local_path=backup)
|
|
482
|
+
assert eng.config.backends[0].backend_type == SyncBackendType.LOCAL
|