@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,632 @@
|
|
|
1
|
+
"""Tests for sovereign metrics collector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from skcapstone.metrics import (
|
|
13
|
+
ConsciousnessMetrics,
|
|
14
|
+
MetricsCollector,
|
|
15
|
+
MetricsReport,
|
|
16
|
+
FortressMetrics,
|
|
17
|
+
KmsMetrics,
|
|
18
|
+
PubSubMetrics,
|
|
19
|
+
SecurityMetrics,
|
|
20
|
+
SyncMetrics,
|
|
21
|
+
TrustMetrics,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def home(tmp_path: Path) -> Path:
|
|
27
|
+
"""Create a minimal agent home with all subsystem directories."""
|
|
28
|
+
# Identity
|
|
29
|
+
identity_dir = tmp_path / "identity"
|
|
30
|
+
identity_dir.mkdir()
|
|
31
|
+
(identity_dir / "identity.json").write_text(json.dumps({
|
|
32
|
+
"name": "test-agent",
|
|
33
|
+
"email": "test@skcapstone.local",
|
|
34
|
+
"fingerprint": "ABCD1234567890ABCDEF1234567890ABCDEF1234",
|
|
35
|
+
}), encoding="utf-8")
|
|
36
|
+
|
|
37
|
+
# Memory
|
|
38
|
+
mem_dir = tmp_path / "memory"
|
|
39
|
+
mem_dir.mkdir()
|
|
40
|
+
for layer in ("short-term", "mid-term", "long-term"):
|
|
41
|
+
(mem_dir / layer).mkdir()
|
|
42
|
+
|
|
43
|
+
# Security
|
|
44
|
+
security_dir = tmp_path / "security"
|
|
45
|
+
security_dir.mkdir()
|
|
46
|
+
|
|
47
|
+
# Coordination
|
|
48
|
+
coord_dir = tmp_path / "coordination"
|
|
49
|
+
(coord_dir / "tasks").mkdir(parents=True)
|
|
50
|
+
(coord_dir / "agents").mkdir(parents=True)
|
|
51
|
+
|
|
52
|
+
return tmp_path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def collector(home: Path) -> MetricsCollector:
|
|
57
|
+
"""Create a MetricsCollector."""
|
|
58
|
+
return MetricsCollector(home)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Basic collection
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestBasicCollection:
|
|
67
|
+
"""Tests for the basic collect workflow."""
|
|
68
|
+
|
|
69
|
+
def test_collect_returns_report(self, collector: MetricsCollector) -> None:
|
|
70
|
+
"""Collect returns a MetricsReport."""
|
|
71
|
+
report = collector.collect()
|
|
72
|
+
assert isinstance(report, MetricsReport)
|
|
73
|
+
|
|
74
|
+
def test_collect_has_timing(self, collector: MetricsCollector) -> None:
|
|
75
|
+
"""Report includes collection time."""
|
|
76
|
+
report = collector.collect()
|
|
77
|
+
assert report.collection_time_ms >= 0
|
|
78
|
+
|
|
79
|
+
def test_collect_has_timestamp(self, collector: MetricsCollector) -> None:
|
|
80
|
+
"""Report includes collection timestamp."""
|
|
81
|
+
report = collector.collect()
|
|
82
|
+
assert report.collected_at is not None
|
|
83
|
+
|
|
84
|
+
def test_collect_has_agent_name(self, home: Path) -> None:
|
|
85
|
+
"""Report reads agent name from manifest."""
|
|
86
|
+
(home / "manifest.json").write_text(
|
|
87
|
+
json.dumps({"name": "test-opus"}), encoding="utf-8",
|
|
88
|
+
)
|
|
89
|
+
collector = MetricsCollector(home)
|
|
90
|
+
report = collector.collect()
|
|
91
|
+
assert report.agent_name == "test-opus"
|
|
92
|
+
|
|
93
|
+
def test_summary_string(self, collector: MetricsCollector) -> None:
|
|
94
|
+
"""Summary produces a one-line string."""
|
|
95
|
+
report = collector.collect()
|
|
96
|
+
summary = report.summary()
|
|
97
|
+
assert isinstance(summary, str)
|
|
98
|
+
assert "mem=" in summary
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Trust metrics
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TestTrustMetrics:
|
|
107
|
+
"""Tests for trust/Cloud9 collection."""
|
|
108
|
+
|
|
109
|
+
def test_trust_from_file(self, home: Path) -> None:
|
|
110
|
+
"""Trust metrics read from trust.json."""
|
|
111
|
+
trust_dir = home / "trust"
|
|
112
|
+
trust_dir.mkdir()
|
|
113
|
+
(trust_dir / "trust.json").write_text(json.dumps({
|
|
114
|
+
"depth": 7.0,
|
|
115
|
+
"trust_level": 0.92,
|
|
116
|
+
"love_intensity": 0.88,
|
|
117
|
+
"entangled": True,
|
|
118
|
+
"last_rehydration": "2026-02-27T10:00:00Z",
|
|
119
|
+
}), encoding="utf-8")
|
|
120
|
+
|
|
121
|
+
febs_dir = trust_dir / "febs"
|
|
122
|
+
febs_dir.mkdir()
|
|
123
|
+
(febs_dir / "test.feb").write_text("{}", encoding="utf-8")
|
|
124
|
+
(febs_dir / "test2.feb").write_text("{}", encoding="utf-8")
|
|
125
|
+
|
|
126
|
+
collector = MetricsCollector(home)
|
|
127
|
+
report = collector.collect()
|
|
128
|
+
assert report.trust.available is True
|
|
129
|
+
assert report.trust.depth == 7.0
|
|
130
|
+
assert report.trust.entangled is True
|
|
131
|
+
assert report.trust.feb_count == 2
|
|
132
|
+
|
|
133
|
+
def test_trust_missing(self, home: Path) -> None:
|
|
134
|
+
"""Missing trust returns unavailable."""
|
|
135
|
+
collector = MetricsCollector(home)
|
|
136
|
+
report = collector.collect()
|
|
137
|
+
assert report.trust.available is False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Security metrics
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestSecurityMetrics:
|
|
146
|
+
"""Tests for security audit collection."""
|
|
147
|
+
|
|
148
|
+
def test_security_counts_entries(self, home: Path) -> None:
|
|
149
|
+
"""Security metrics count audit log entries."""
|
|
150
|
+
audit_log = home / "security" / "audit.log"
|
|
151
|
+
entries = [
|
|
152
|
+
json.dumps({"event_type": "INIT", "detail": "init"}),
|
|
153
|
+
json.dumps({"event_type": "MEMORY_SEALED", "detail": "sealed"}),
|
|
154
|
+
json.dumps({"event_type": "MEMORY_TAMPER_ALERT", "detail": "tamper"}),
|
|
155
|
+
json.dumps({"event_type": "MEMORY_SEALED", "detail": "sealed2"}),
|
|
156
|
+
]
|
|
157
|
+
audit_log.write_text("\n".join(entries) + "\n", encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
collector = MetricsCollector(home)
|
|
160
|
+
report = collector.collect()
|
|
161
|
+
assert report.security.available is True
|
|
162
|
+
assert report.security.audit_entries == 4
|
|
163
|
+
assert report.security.tamper_alerts == 1
|
|
164
|
+
assert report.security.event_types["MEMORY_SEALED"] == 2
|
|
165
|
+
|
|
166
|
+
def test_security_missing(self, home: Path) -> None:
|
|
167
|
+
"""Missing audit log returns unavailable."""
|
|
168
|
+
(home / "security" / "audit.log").unlink(missing_ok=True)
|
|
169
|
+
collector = MetricsCollector(home)
|
|
170
|
+
report = collector.collect()
|
|
171
|
+
assert report.security.available is False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Sync metrics
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestSyncMetrics:
|
|
180
|
+
"""Tests for sync layer collection."""
|
|
181
|
+
|
|
182
|
+
def test_sync_counts_seeds(self, home: Path) -> None:
|
|
183
|
+
"""Sync metrics count seeds in outbox/inbox."""
|
|
184
|
+
sync_dir = home / "sync"
|
|
185
|
+
outbox = sync_dir / "outbox"
|
|
186
|
+
inbox = sync_dir / "inbox"
|
|
187
|
+
outbox.mkdir(parents=True)
|
|
188
|
+
inbox.mkdir(parents=True)
|
|
189
|
+
|
|
190
|
+
(outbox / "seed1.json").write_text("{}", encoding="utf-8")
|
|
191
|
+
(outbox / "seed2.json").write_text("{}", encoding="utf-8")
|
|
192
|
+
(inbox / "seed3.json").write_text("{}", encoding="utf-8")
|
|
193
|
+
|
|
194
|
+
collector = MetricsCollector(home)
|
|
195
|
+
report = collector.collect()
|
|
196
|
+
assert report.sync.available is True
|
|
197
|
+
assert report.sync.seeds_outbox == 2
|
|
198
|
+
assert report.sync.seeds_inbox == 1
|
|
199
|
+
|
|
200
|
+
def test_sync_missing(self, home: Path) -> None:
|
|
201
|
+
"""Missing sync dir returns unavailable."""
|
|
202
|
+
collector = MetricsCollector(home)
|
|
203
|
+
report = collector.collect()
|
|
204
|
+
assert report.sync.available is False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Pub/sub metrics
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TestPubSubMetrics:
|
|
213
|
+
"""Tests for pub/sub collection."""
|
|
214
|
+
|
|
215
|
+
def test_pubsub_counts(self, home: Path) -> None:
|
|
216
|
+
"""Pub/sub metrics count topics, messages, subscriptions."""
|
|
217
|
+
pubsub_dir = home / "pubsub"
|
|
218
|
+
topics_dir = pubsub_dir / "topics"
|
|
219
|
+
(topics_dir / "system.health").mkdir(parents=True)
|
|
220
|
+
(topics_dir / "team.dev").mkdir(parents=True)
|
|
221
|
+
|
|
222
|
+
for i in range(3):
|
|
223
|
+
(topics_dir / "system.health" / f"msg-{i}.json").write_text(
|
|
224
|
+
"{}", encoding="utf-8",
|
|
225
|
+
)
|
|
226
|
+
(topics_dir / "team.dev" / "msg-0.json").write_text("{}", encoding="utf-8")
|
|
227
|
+
|
|
228
|
+
(pubsub_dir / "subscriptions.json").write_text(json.dumps({
|
|
229
|
+
"system.*": {"pattern": "system.*"},
|
|
230
|
+
"team.dev": {"pattern": "team.dev"},
|
|
231
|
+
}), encoding="utf-8")
|
|
232
|
+
|
|
233
|
+
collector = MetricsCollector(home)
|
|
234
|
+
report = collector.collect()
|
|
235
|
+
assert report.pubsub.available is True
|
|
236
|
+
assert report.pubsub.topics == 2
|
|
237
|
+
assert report.pubsub.messages == 4
|
|
238
|
+
assert report.pubsub.subscriptions == 2
|
|
239
|
+
|
|
240
|
+
def test_pubsub_missing(self, home: Path) -> None:
|
|
241
|
+
"""Missing pubsub dir returns unavailable."""
|
|
242
|
+
collector = MetricsCollector(home)
|
|
243
|
+
report = collector.collect()
|
|
244
|
+
assert report.pubsub.available is False
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
# KMS metrics
|
|
249
|
+
# ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class TestKmsMetrics:
|
|
253
|
+
"""Tests for KMS collection."""
|
|
254
|
+
|
|
255
|
+
def test_kms_counts_keys(self, home: Path) -> None:
|
|
256
|
+
"""KMS metrics count keys by type and status."""
|
|
257
|
+
kms_dir = home / "security" / "kms"
|
|
258
|
+
kms_dir.mkdir(parents=True)
|
|
259
|
+
(kms_dir / "keystore.json").write_text(json.dumps({
|
|
260
|
+
"keys": {
|
|
261
|
+
"k1": {"key_type": "master", "status": "active"},
|
|
262
|
+
"k2": {"key_type": "service", "status": "active"},
|
|
263
|
+
"k3": {"key_type": "service", "status": "rotated"},
|
|
264
|
+
"k4": {"key_type": "team", "status": "active"},
|
|
265
|
+
},
|
|
266
|
+
}), encoding="utf-8")
|
|
267
|
+
|
|
268
|
+
collector = MetricsCollector(home)
|
|
269
|
+
report = collector.collect()
|
|
270
|
+
assert report.kms.available is True
|
|
271
|
+
assert report.kms.total_keys == 4
|
|
272
|
+
assert report.kms.active_keys == 3
|
|
273
|
+
assert report.kms.by_type["service"] == 2
|
|
274
|
+
|
|
275
|
+
def test_kms_rotation_count(self, home: Path) -> None:
|
|
276
|
+
"""KMS metrics count rotations."""
|
|
277
|
+
kms_dir = home / "security" / "kms"
|
|
278
|
+
kms_dir.mkdir(parents=True)
|
|
279
|
+
(kms_dir / "keystore.json").write_text(json.dumps({"keys": {}}), encoding="utf-8")
|
|
280
|
+
(kms_dir / "rotation-log.json").write_text(json.dumps([
|
|
281
|
+
{"key_id": "k1", "old_version": 1, "new_version": 2},
|
|
282
|
+
{"key_id": "k1", "old_version": 2, "new_version": 3},
|
|
283
|
+
]), encoding="utf-8")
|
|
284
|
+
|
|
285
|
+
collector = MetricsCollector(home)
|
|
286
|
+
report = collector.collect()
|
|
287
|
+
assert report.kms.rotations == 2
|
|
288
|
+
|
|
289
|
+
def test_kms_missing(self, home: Path) -> None:
|
|
290
|
+
"""Missing KMS returns unavailable."""
|
|
291
|
+
collector = MetricsCollector(home)
|
|
292
|
+
report = collector.collect()
|
|
293
|
+
assert report.kms.available is False
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
# Fortress metrics
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class TestFortressMetrics:
|
|
302
|
+
"""Tests for memory fortress collection."""
|
|
303
|
+
|
|
304
|
+
def test_fortress_config(self, home: Path) -> None:
|
|
305
|
+
"""Fortress metrics read from config."""
|
|
306
|
+
(home / "memory" / "fortress.json").write_text(json.dumps({
|
|
307
|
+
"enabled": True,
|
|
308
|
+
"encryption_enabled": True,
|
|
309
|
+
"seal_algorithm": "hmac-sha256",
|
|
310
|
+
}), encoding="utf-8")
|
|
311
|
+
|
|
312
|
+
collector = MetricsCollector(home)
|
|
313
|
+
report = collector.collect()
|
|
314
|
+
assert report.fortress.enabled is True
|
|
315
|
+
assert report.fortress.encryption_enabled is True
|
|
316
|
+
assert report.fortress.seal_algorithm == "hmac-sha256"
|
|
317
|
+
|
|
318
|
+
def test_fortress_missing(self, home: Path) -> None:
|
|
319
|
+
"""Missing fortress config returns disabled."""
|
|
320
|
+
collector = MetricsCollector(home)
|
|
321
|
+
report = collector.collect()
|
|
322
|
+
assert report.fortress.enabled is False
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
# Coordination metrics
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class TestCoordinationMetrics:
|
|
331
|
+
"""Tests for coordination board collection."""
|
|
332
|
+
|
|
333
|
+
def test_coord_counts_tasks(self, home: Path) -> None:
|
|
334
|
+
"""Coordination metrics count tasks by status."""
|
|
335
|
+
tasks_dir = home / "coordination" / "tasks"
|
|
336
|
+
for i in range(3):
|
|
337
|
+
(tasks_dir / f"task{i}.json").write_text(json.dumps({
|
|
338
|
+
"id": f"task{i}", "status": "open", "title": f"Task {i}",
|
|
339
|
+
}), encoding="utf-8")
|
|
340
|
+
(tasks_dir / "done1.json").write_text(json.dumps({
|
|
341
|
+
"id": "done1", "status": "done", "title": "Done",
|
|
342
|
+
}), encoding="utf-8")
|
|
343
|
+
|
|
344
|
+
collector = MetricsCollector(home)
|
|
345
|
+
report = collector.collect()
|
|
346
|
+
assert report.coordination.total_tasks == 4
|
|
347
|
+
assert report.coordination.open == 3
|
|
348
|
+
assert report.coordination.done == 1
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# ---------------------------------------------------------------------------
|
|
352
|
+
# Error resilience
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestErrorResilience:
|
|
357
|
+
"""Tests for graceful error handling."""
|
|
358
|
+
|
|
359
|
+
def test_corrupt_json_doesnt_crash(self, home: Path) -> None:
|
|
360
|
+
"""Corrupt JSON files don't crash collection."""
|
|
361
|
+
(home / "trust").mkdir(exist_ok=True)
|
|
362
|
+
(home / "trust" / "trust.json").write_text("not json {{{", encoding="utf-8")
|
|
363
|
+
|
|
364
|
+
collector = MetricsCollector(home)
|
|
365
|
+
report = collector.collect()
|
|
366
|
+
assert isinstance(report, MetricsReport)
|
|
367
|
+
|
|
368
|
+
def test_missing_home_doesnt_crash(self, tmp_path: Path) -> None:
|
|
369
|
+
"""Non-existent home doesn't crash."""
|
|
370
|
+
collector = MetricsCollector(tmp_path / "nonexistent")
|
|
371
|
+
report = collector.collect()
|
|
372
|
+
assert isinstance(report, MetricsReport)
|
|
373
|
+
|
|
374
|
+
def test_all_sections_isolated(self, home: Path) -> None:
|
|
375
|
+
"""One failing section doesn't prevent others."""
|
|
376
|
+
(home / "security" / "audit.log").write_text("not json\n", encoding="utf-8")
|
|
377
|
+
(home / "trust").mkdir(exist_ok=True)
|
|
378
|
+
(home / "trust" / "trust.json").write_text(json.dumps({
|
|
379
|
+
"depth": 5.0, "trust_level": 0.8,
|
|
380
|
+
}), encoding="utf-8")
|
|
381
|
+
|
|
382
|
+
collector = MetricsCollector(home)
|
|
383
|
+
report = collector.collect()
|
|
384
|
+
assert report.trust.available is True
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# ---------------------------------------------------------------------------
|
|
388
|
+
# Model tests
|
|
389
|
+
# ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class TestModels:
|
|
393
|
+
"""Tests for metrics models."""
|
|
394
|
+
|
|
395
|
+
def test_report_serializable(self, collector: MetricsCollector) -> None:
|
|
396
|
+
"""Report can be serialized to JSON."""
|
|
397
|
+
report = collector.collect()
|
|
398
|
+
data = report.model_dump_json()
|
|
399
|
+
assert isinstance(data, str)
|
|
400
|
+
parsed = json.loads(data)
|
|
401
|
+
assert "identity" in parsed
|
|
402
|
+
assert "trust" in parsed
|
|
403
|
+
assert "kms" in parsed
|
|
404
|
+
|
|
405
|
+
def test_trust_metrics_defaults(self) -> None:
|
|
406
|
+
"""TrustMetrics has sensible defaults."""
|
|
407
|
+
t = TrustMetrics()
|
|
408
|
+
assert t.available is False
|
|
409
|
+
assert t.depth == 0.0
|
|
410
|
+
|
|
411
|
+
def test_kms_metrics_defaults(self) -> None:
|
|
412
|
+
"""KmsMetrics has sensible defaults."""
|
|
413
|
+
k = KmsMetrics()
|
|
414
|
+
assert k.available is False
|
|
415
|
+
assert k.active_keys == 0
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
# ---------------------------------------------------------------------------
|
|
419
|
+
# ConsciousnessMetrics
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class TestConsciousnessMetrics:
|
|
424
|
+
"""Tests for the consciousness loop runtime metrics collector."""
|
|
425
|
+
|
|
426
|
+
@pytest.fixture
|
|
427
|
+
def cm(self, tmp_path: Path) -> ConsciousnessMetrics:
|
|
428
|
+
"""ConsciousnessMetrics with no background thread."""
|
|
429
|
+
return ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
430
|
+
|
|
431
|
+
# ------------------------------------------------------------------
|
|
432
|
+
# Basic counters
|
|
433
|
+
# ------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
def test_initial_counters_zero(self, cm: ConsciousnessMetrics) -> None:
|
|
436
|
+
"""All counters start at zero."""
|
|
437
|
+
d = cm.to_dict()
|
|
438
|
+
assert d["messages_processed"] == 0
|
|
439
|
+
assert d["responses_sent"] == 0
|
|
440
|
+
assert d["errors"] == 0
|
|
441
|
+
|
|
442
|
+
def test_record_message_increments(self, cm: ConsciousnessMetrics) -> None:
|
|
443
|
+
"""record_message increments messages_processed and peer counter."""
|
|
444
|
+
cm.record_message("alice")
|
|
445
|
+
cm.record_message("alice")
|
|
446
|
+
cm.record_message("bob")
|
|
447
|
+
d = cm.to_dict()
|
|
448
|
+
assert d["messages_processed"] == 3
|
|
449
|
+
assert d["messages_per_peer"]["alice"] == 2
|
|
450
|
+
assert d["messages_per_peer"]["bob"] == 1
|
|
451
|
+
|
|
452
|
+
def test_record_response_increments(self, cm: ConsciousnessMetrics) -> None:
|
|
453
|
+
"""record_response increments responses_sent, backend, and tier."""
|
|
454
|
+
cm.record_response(120.5, "ollama", "fast")
|
|
455
|
+
cm.record_response(80.0, "anthropic", "standard")
|
|
456
|
+
cm.record_response(95.0, "ollama", "fast")
|
|
457
|
+
d = cm.to_dict()
|
|
458
|
+
assert d["responses_sent"] == 3
|
|
459
|
+
assert d["backend_usage"]["ollama"] == 2
|
|
460
|
+
assert d["backend_usage"]["anthropic"] == 1
|
|
461
|
+
assert d["tier_usage"]["fast"] == 2
|
|
462
|
+
assert d["tier_usage"]["standard"] == 1
|
|
463
|
+
|
|
464
|
+
def test_record_error_increments(self, cm: ConsciousnessMetrics) -> None:
|
|
465
|
+
"""record_error increments the errors counter."""
|
|
466
|
+
cm.record_error()
|
|
467
|
+
cm.record_error()
|
|
468
|
+
assert cm.to_dict()["errors"] == 2
|
|
469
|
+
|
|
470
|
+
# ------------------------------------------------------------------
|
|
471
|
+
# Histogram
|
|
472
|
+
# ------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
def test_histogram_stats_empty(self, cm: ConsciousnessMetrics) -> None:
|
|
475
|
+
"""Histogram returns zeros when no samples."""
|
|
476
|
+
stats = cm.to_dict()["response_time_ms"]
|
|
477
|
+
assert stats["count"] == 0
|
|
478
|
+
assert stats["min"] == 0.0
|
|
479
|
+
assert stats["avg"] == 0.0
|
|
480
|
+
assert stats["p99"] == 0.0
|
|
481
|
+
|
|
482
|
+
def test_histogram_min_max_avg(self, cm: ConsciousnessMetrics) -> None:
|
|
483
|
+
"""Histogram computes min/max/avg correctly."""
|
|
484
|
+
for ms in [10.0, 20.0, 30.0, 40.0, 50.0]:
|
|
485
|
+
cm.record_response(ms, "passthrough", "fast")
|
|
486
|
+
stats = cm.to_dict()["response_time_ms"]
|
|
487
|
+
assert stats["min"] == 10.0
|
|
488
|
+
assert stats["max"] == 50.0
|
|
489
|
+
assert stats["avg"] == 30.0
|
|
490
|
+
assert stats["count"] == 5
|
|
491
|
+
|
|
492
|
+
def test_histogram_p99_single(self, cm: ConsciousnessMetrics) -> None:
|
|
493
|
+
"""p99 of a single sample equals that sample."""
|
|
494
|
+
cm.record_response(42.0, "passthrough", "fast")
|
|
495
|
+
stats = cm.to_dict()["response_time_ms"]
|
|
496
|
+
assert stats["p99"] == 42.0
|
|
497
|
+
|
|
498
|
+
def test_histogram_p99_hundred_samples(self, cm: ConsciousnessMetrics) -> None:
|
|
499
|
+
"""p99 of 100 evenly-spaced samples is the 99th value."""
|
|
500
|
+
for i in range(1, 101):
|
|
501
|
+
cm.record_response(float(i), "passthrough", "fast")
|
|
502
|
+
stats = cm.to_dict()["response_time_ms"]
|
|
503
|
+
# p99_idx = min(99, int(100 * 0.99)) = 98 → sorted[98] = 99.0
|
|
504
|
+
assert stats["p99"] == 99.0
|
|
505
|
+
|
|
506
|
+
def test_histogram_capped_at_1000(self, cm: ConsciousnessMetrics) -> None:
|
|
507
|
+
"""Histogram caps sample list at 1 000 to bound memory."""
|
|
508
|
+
for i in range(1200):
|
|
509
|
+
cm.record_response(float(i), "passthrough", "fast")
|
|
510
|
+
assert cm.to_dict()["response_time_ms"]["count"] == 1000
|
|
511
|
+
|
|
512
|
+
# ------------------------------------------------------------------
|
|
513
|
+
# Persistence
|
|
514
|
+
# ------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
def test_save_and_reload(self, tmp_path: Path) -> None:
|
|
517
|
+
"""save() writes JSON; a new instance with the same home loads it."""
|
|
518
|
+
cm1 = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
519
|
+
cm1.record_message("peer-a")
|
|
520
|
+
cm1.record_response(55.0, "ollama", "fast")
|
|
521
|
+
cm1.record_error()
|
|
522
|
+
cm1.save()
|
|
523
|
+
|
|
524
|
+
cm2 = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
525
|
+
d = cm2.to_dict()
|
|
526
|
+
assert d["messages_processed"] == 1
|
|
527
|
+
assert d["responses_sent"] == 1
|
|
528
|
+
assert d["errors"] == 1
|
|
529
|
+
assert d["backend_usage"]["ollama"] == 1
|
|
530
|
+
assert d["tier_usage"]["fast"] == 1
|
|
531
|
+
assert d["messages_per_peer"]["peer-a"] == 1
|
|
532
|
+
|
|
533
|
+
def test_save_creates_daily_file(self, tmp_path: Path) -> None:
|
|
534
|
+
"""save() creates the daily JSON file under metrics/daily/."""
|
|
535
|
+
from datetime import datetime, timezone
|
|
536
|
+
cm = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
537
|
+
cm.record_message("x")
|
|
538
|
+
cm.save()
|
|
539
|
+
|
|
540
|
+
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
541
|
+
daily = tmp_path / "metrics" / "daily" / f"{date_str}.json"
|
|
542
|
+
assert daily.exists()
|
|
543
|
+
data = json.loads(daily.read_text(encoding="utf-8"))
|
|
544
|
+
assert data["messages_processed"] == 1
|
|
545
|
+
|
|
546
|
+
def test_load_missing_file_doesnt_crash(self, tmp_path: Path) -> None:
|
|
547
|
+
"""Creating ConsciousnessMetrics when no file exists doesn't fail."""
|
|
548
|
+
cm = ConsciousnessMetrics(home=tmp_path / "nonexistent", persist_interval=0)
|
|
549
|
+
assert cm.to_dict()["messages_processed"] == 0
|
|
550
|
+
|
|
551
|
+
def test_load_corrupt_file_doesnt_crash(self, tmp_path: Path) -> None:
|
|
552
|
+
"""Corrupt daily JSON is silently ignored."""
|
|
553
|
+
from datetime import datetime, timezone
|
|
554
|
+
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
555
|
+
daily = tmp_path / "metrics" / "daily" / f"{date_str}.json"
|
|
556
|
+
daily.parent.mkdir(parents=True)
|
|
557
|
+
daily.write_text("not json {{{", encoding="utf-8")
|
|
558
|
+
cm = ConsciousnessMetrics(home=tmp_path, persist_interval=0)
|
|
559
|
+
assert cm.to_dict()["messages_processed"] == 0
|
|
560
|
+
|
|
561
|
+
# ------------------------------------------------------------------
|
|
562
|
+
# Thread safety
|
|
563
|
+
# ------------------------------------------------------------------
|
|
564
|
+
|
|
565
|
+
def test_concurrent_record_message(self, cm: ConsciousnessMetrics) -> None:
|
|
566
|
+
"""Concurrent record_message calls produce correct total."""
|
|
567
|
+
n = 200
|
|
568
|
+
barrier = threading.Barrier(n)
|
|
569
|
+
|
|
570
|
+
def _record():
|
|
571
|
+
barrier.wait()
|
|
572
|
+
cm.record_message("stress-peer")
|
|
573
|
+
|
|
574
|
+
threads = [threading.Thread(target=_record) for _ in range(n)]
|
|
575
|
+
for t in threads:
|
|
576
|
+
t.start()
|
|
577
|
+
for t in threads:
|
|
578
|
+
t.join()
|
|
579
|
+
|
|
580
|
+
d = cm.to_dict()
|
|
581
|
+
assert d["messages_processed"] == n
|
|
582
|
+
assert d["messages_per_peer"]["stress-peer"] == n
|
|
583
|
+
|
|
584
|
+
def test_concurrent_record_response(self, cm: ConsciousnessMetrics) -> None:
|
|
585
|
+
"""Concurrent record_response calls produce correct total."""
|
|
586
|
+
n = 100
|
|
587
|
+
barrier = threading.Barrier(n)
|
|
588
|
+
|
|
589
|
+
def _record():
|
|
590
|
+
barrier.wait()
|
|
591
|
+
cm.record_response(10.0, "passthrough", "fast")
|
|
592
|
+
|
|
593
|
+
threads = [threading.Thread(target=_record) for _ in range(n)]
|
|
594
|
+
for t in threads:
|
|
595
|
+
t.start()
|
|
596
|
+
for t in threads:
|
|
597
|
+
t.join()
|
|
598
|
+
|
|
599
|
+
assert cm.to_dict()["responses_sent"] == n
|
|
600
|
+
|
|
601
|
+
# ------------------------------------------------------------------
|
|
602
|
+
# to_dict structure
|
|
603
|
+
# ------------------------------------------------------------------
|
|
604
|
+
|
|
605
|
+
def test_to_dict_keys(self, cm: ConsciousnessMetrics) -> None:
|
|
606
|
+
"""to_dict returns all required keys."""
|
|
607
|
+
d = cm.to_dict()
|
|
608
|
+
required = {
|
|
609
|
+
"date", "session_start", "messages_processed", "responses_sent",
|
|
610
|
+
"errors", "response_time_ms", "backend_usage", "tier_usage",
|
|
611
|
+
"messages_per_peer",
|
|
612
|
+
}
|
|
613
|
+
assert required.issubset(d.keys())
|
|
614
|
+
|
|
615
|
+
def test_to_dict_json_serializable(self, cm: ConsciousnessMetrics) -> None:
|
|
616
|
+
"""to_dict output can be serialized to JSON."""
|
|
617
|
+
cm.record_message("peer")
|
|
618
|
+
cm.record_response(50.0, "ollama", "local")
|
|
619
|
+
data = json.dumps(cm.to_dict())
|
|
620
|
+
assert isinstance(data, str)
|
|
621
|
+
|
|
622
|
+
# ------------------------------------------------------------------
|
|
623
|
+
# Peer name sanitization
|
|
624
|
+
# ------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
def test_peer_name_truncated_to_64(self, cm: ConsciousnessMetrics) -> None:
|
|
627
|
+
"""Peer names longer than 64 chars are truncated in the counter key."""
|
|
628
|
+
long_peer = "a" * 100
|
|
629
|
+
cm.record_message(long_peer)
|
|
630
|
+
d = cm.to_dict()
|
|
631
|
+
assert "a" * 64 in d["messages_per_peer"]
|
|
632
|
+
assert long_peer not in d["messages_per_peer"]
|