@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,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified test runner for the sovereign agent ecosystem.
|
|
3
|
+
|
|
4
|
+
Discovers all packages in the monorepo, runs pytest for each,
|
|
5
|
+
and presents a consolidated pass/fail summary. Works from any
|
|
6
|
+
terminal — no CI server, no IDE, no special tooling.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
skcapstone test # run all packages
|
|
10
|
+
skcapstone test --package skcomm # run one package
|
|
11
|
+
skcapstone test --fast # stop on first failure
|
|
12
|
+
skcapstone test --json-out # machine-readable results
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
ECOSYSTEM_PACKAGES = [
|
|
27
|
+
{"name": "skcapstone", "path": "skcapstone", "tests": "skcapstone/tests"},
|
|
28
|
+
{"name": "capauth", "path": "capauth", "tests": "capauth/tests"},
|
|
29
|
+
{"name": "skcomm", "path": "skcomm", "tests": "skcomm/tests"},
|
|
30
|
+
{"name": "skchat", "path": "skchat", "tests": "skchat/tests"},
|
|
31
|
+
{"name": "skmemory", "path": "skmemory", "tests": "skmemory/tests"},
|
|
32
|
+
{"name": "cloud9-python", "path": "cloud9-python", "tests": "cloud9-python/tests"},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class PackageResult:
|
|
38
|
+
"""Test results for a single package.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
name: Package name.
|
|
42
|
+
passed: Number of tests passed.
|
|
43
|
+
failed: Number of tests failed.
|
|
44
|
+
errors: Number of collection/import errors.
|
|
45
|
+
skipped: Number of skipped tests.
|
|
46
|
+
duration_s: Total runtime in seconds.
|
|
47
|
+
exit_code: Pytest exit code.
|
|
48
|
+
output: Stdout from pytest (last N lines).
|
|
49
|
+
available: Whether the package tests were found.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
passed: int = 0
|
|
54
|
+
failed: int = 0
|
|
55
|
+
errors: int = 0
|
|
56
|
+
skipped: int = 0
|
|
57
|
+
duration_s: float = 0.0
|
|
58
|
+
exit_code: int = -1
|
|
59
|
+
output: str = ""
|
|
60
|
+
available: bool = True
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def total(self) -> int:
|
|
64
|
+
"""Total tests executed."""
|
|
65
|
+
return self.passed + self.failed + self.errors
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def success(self) -> bool:
|
|
69
|
+
"""Whether all tests passed."""
|
|
70
|
+
return self.exit_code == 0 and self.failed == 0 and self.errors == 0
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> dict:
|
|
73
|
+
"""Serialize to dict.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: Package test results.
|
|
77
|
+
"""
|
|
78
|
+
return {
|
|
79
|
+
"name": self.name,
|
|
80
|
+
"passed": self.passed,
|
|
81
|
+
"failed": self.failed,
|
|
82
|
+
"errors": self.errors,
|
|
83
|
+
"skipped": self.skipped,
|
|
84
|
+
"total": self.total,
|
|
85
|
+
"duration_s": round(self.duration_s, 2),
|
|
86
|
+
"success": self.success,
|
|
87
|
+
"available": self.available,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class TestReport:
|
|
93
|
+
"""Consolidated test results across all packages.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
results: Per-package results.
|
|
97
|
+
duration_s: Total runtime.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
results: list[PackageResult] = field(default_factory=list)
|
|
101
|
+
duration_s: float = 0.0
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def total_passed(self) -> int:
|
|
105
|
+
"""Total passing tests across all packages."""
|
|
106
|
+
return sum(r.passed for r in self.results)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def total_failed(self) -> int:
|
|
110
|
+
"""Total failing tests across all packages."""
|
|
111
|
+
return sum(r.failed for r in self.results)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def total_errors(self) -> int:
|
|
115
|
+
"""Total errors across all packages."""
|
|
116
|
+
return sum(r.errors for r in self.results)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def all_passed(self) -> bool:
|
|
120
|
+
"""Whether every package passed."""
|
|
121
|
+
return all(r.success for r in self.results if r.available)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def packages_tested(self) -> int:
|
|
125
|
+
"""Number of packages actually tested."""
|
|
126
|
+
return sum(1 for r in self.results if r.available)
|
|
127
|
+
|
|
128
|
+
def to_dict(self) -> dict:
|
|
129
|
+
"""Serialize the full report.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
dict: Complete test report.
|
|
133
|
+
"""
|
|
134
|
+
return {
|
|
135
|
+
"all_passed": self.all_passed,
|
|
136
|
+
"total_passed": self.total_passed,
|
|
137
|
+
"total_failed": self.total_failed,
|
|
138
|
+
"total_errors": self.total_errors,
|
|
139
|
+
"packages_tested": self.packages_tested,
|
|
140
|
+
"duration_s": round(self.duration_s, 2),
|
|
141
|
+
"packages": [r.to_dict() for r in self.results],
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def run_all_tests(
|
|
146
|
+
monorepo_root: Path,
|
|
147
|
+
packages: Optional[list[str]] = None,
|
|
148
|
+
fail_fast: bool = False,
|
|
149
|
+
verbose: bool = False,
|
|
150
|
+
timeout: int = 120,
|
|
151
|
+
) -> TestReport:
|
|
152
|
+
"""Run pytest across ecosystem packages.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
monorepo_root: Root of the monorepo (where package dirs live).
|
|
156
|
+
packages: Restrict to these package names. None = all.
|
|
157
|
+
fail_fast: Stop after first package failure.
|
|
158
|
+
verbose: Pass -v to pytest.
|
|
159
|
+
timeout: Per-package timeout in seconds.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
TestReport: Consolidated results.
|
|
163
|
+
"""
|
|
164
|
+
report = TestReport()
|
|
165
|
+
start = time.monotonic()
|
|
166
|
+
|
|
167
|
+
targets = ECOSYSTEM_PACKAGES
|
|
168
|
+
if packages:
|
|
169
|
+
pkg_set = set(packages)
|
|
170
|
+
targets = [p for p in targets if p["name"] in pkg_set]
|
|
171
|
+
|
|
172
|
+
for pkg_info in targets:
|
|
173
|
+
test_dir = monorepo_root / pkg_info["tests"]
|
|
174
|
+
if not test_dir.exists():
|
|
175
|
+
report.results.append(PackageResult(
|
|
176
|
+
name=pkg_info["name"],
|
|
177
|
+
available=False,
|
|
178
|
+
output=f"Test directory not found: {test_dir}",
|
|
179
|
+
))
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
result = _run_package_tests(
|
|
183
|
+
monorepo_root, pkg_info, verbose=verbose, timeout=timeout,
|
|
184
|
+
)
|
|
185
|
+
report.results.append(result)
|
|
186
|
+
|
|
187
|
+
if fail_fast and not result.success:
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
report.duration_s = time.monotonic() - start
|
|
191
|
+
return report
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _run_package_tests(
|
|
195
|
+
root: Path,
|
|
196
|
+
pkg_info: dict,
|
|
197
|
+
verbose: bool = False,
|
|
198
|
+
timeout: int = 120,
|
|
199
|
+
) -> PackageResult:
|
|
200
|
+
"""Run pytest for a single package.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
root: Monorepo root.
|
|
204
|
+
pkg_info: Package metadata dict.
|
|
205
|
+
verbose: Pass -v to pytest.
|
|
206
|
+
timeout: Timeout in seconds.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
PackageResult: Test results for this package.
|
|
210
|
+
"""
|
|
211
|
+
result = PackageResult(name=pkg_info["name"])
|
|
212
|
+
test_dir = root / pkg_info["tests"]
|
|
213
|
+
|
|
214
|
+
cmd = [
|
|
215
|
+
sys.executable, "-m", "pytest",
|
|
216
|
+
str(test_dir),
|
|
217
|
+
"--tb=line",
|
|
218
|
+
"-q",
|
|
219
|
+
"--no-header",
|
|
220
|
+
]
|
|
221
|
+
if verbose:
|
|
222
|
+
cmd.append("-v")
|
|
223
|
+
|
|
224
|
+
start = time.monotonic()
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
proc = subprocess.run(
|
|
228
|
+
cmd,
|
|
229
|
+
capture_output=True,
|
|
230
|
+
text=True,
|
|
231
|
+
timeout=timeout,
|
|
232
|
+
cwd=str(root),
|
|
233
|
+
)
|
|
234
|
+
result.exit_code = proc.returncode
|
|
235
|
+
result.duration_s = time.monotonic() - start
|
|
236
|
+
|
|
237
|
+
output = proc.stdout + proc.stderr
|
|
238
|
+
result.output = _tail(output, 30)
|
|
239
|
+
|
|
240
|
+
_parse_pytest_summary(output, result)
|
|
241
|
+
|
|
242
|
+
except subprocess.TimeoutExpired:
|
|
243
|
+
result.duration_s = time.monotonic() - start
|
|
244
|
+
result.exit_code = -1
|
|
245
|
+
result.errors = 1
|
|
246
|
+
result.output = f"TIMEOUT after {timeout}s"
|
|
247
|
+
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
result.duration_s = time.monotonic() - start
|
|
250
|
+
result.exit_code = -1
|
|
251
|
+
result.errors = 1
|
|
252
|
+
result.output = str(exc)
|
|
253
|
+
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _parse_pytest_summary(output: str, result: PackageResult) -> None:
|
|
258
|
+
"""Extract pass/fail/skip counts from pytest output.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
output: Full pytest stdout+stderr.
|
|
262
|
+
result: PackageResult to populate.
|
|
263
|
+
"""
|
|
264
|
+
import re
|
|
265
|
+
|
|
266
|
+
for line in reversed(output.split("\n")):
|
|
267
|
+
match = re.search(
|
|
268
|
+
r"(\d+)\s+passed", line,
|
|
269
|
+
)
|
|
270
|
+
if match:
|
|
271
|
+
result.passed = int(match.group(1))
|
|
272
|
+
|
|
273
|
+
match = re.search(r"(\d+)\s+failed", line)
|
|
274
|
+
if match:
|
|
275
|
+
result.failed = int(match.group(1))
|
|
276
|
+
|
|
277
|
+
match = re.search(r"(\d+)\s+error", line)
|
|
278
|
+
if match:
|
|
279
|
+
result.errors = int(match.group(1))
|
|
280
|
+
|
|
281
|
+
match = re.search(r"(\d+)\s+skipped", line)
|
|
282
|
+
if match:
|
|
283
|
+
result.skipped = int(match.group(1))
|
|
284
|
+
|
|
285
|
+
if result.passed > 0 or result.failed > 0:
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _tail(text: str, n: int) -> str:
|
|
290
|
+
"""Get the last N lines of text.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
text: Input text.
|
|
294
|
+
n: Number of lines.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
str: Last N lines.
|
|
298
|
+
"""
|
|
299
|
+
lines = text.strip().split("\n")
|
|
300
|
+
return "\n".join(lines[-n:])
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TLS helpers for the skcapstone daemon.
|
|
3
|
+
|
|
4
|
+
When ``SKCAPSTONE_TLS=true`` is set the daemon generates (or reuses)
|
|
5
|
+
a self-signed X.509 certificate + RSA private key stored under
|
|
6
|
+
``~/.skcapstone/tls/``. The SHA-256 fingerprint of the certificate
|
|
7
|
+
is logged at startup so operators can pin it in clients.
|
|
8
|
+
|
|
9
|
+
Requirements
|
|
10
|
+
------------
|
|
11
|
+
- ``cryptography >= 3.0`` (``pip install cryptography``)
|
|
12
|
+
- Python's built-in ``ssl`` module (wraps the stdlib HTTP server socket)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import hashlib
|
|
19
|
+
import ipaddress
|
|
20
|
+
import logging
|
|
21
|
+
import ssl
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("skcapstone.tls")
|
|
25
|
+
|
|
26
|
+
_CERT_FILENAME = "daemon.crt"
|
|
27
|
+
_KEY_FILENAME = "daemon.key"
|
|
28
|
+
_CERT_VALID_DAYS = 3650 # ~10 years
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _ensure_tls_dir(tls_dir: Path) -> None:
|
|
32
|
+
tls_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
tls_dir.chmod(0o700)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _generate_self_signed(cert_path: Path, key_path: Path) -> None:
|
|
37
|
+
"""Generate an RSA-2048 self-signed certificate and write both files."""
|
|
38
|
+
try:
|
|
39
|
+
from cryptography import x509
|
|
40
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
41
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
42
|
+
from cryptography.x509.oid import NameOID
|
|
43
|
+
except ImportError as exc: # pragma: no cover
|
|
44
|
+
raise RuntimeError(
|
|
45
|
+
"The 'cryptography' package is required for TLS support. "
|
|
46
|
+
"Install it with: pip install cryptography"
|
|
47
|
+
) from exc
|
|
48
|
+
|
|
49
|
+
# Private key
|
|
50
|
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
51
|
+
|
|
52
|
+
# Subject / Issuer
|
|
53
|
+
subject = x509.Name(
|
|
54
|
+
[
|
|
55
|
+
x509.NameAttribute(NameOID.COMMON_NAME, "skcapstone-daemon"),
|
|
56
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "SKCapstone Sovereign Agent"),
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
61
|
+
cert = (
|
|
62
|
+
x509.CertificateBuilder()
|
|
63
|
+
.subject_name(subject)
|
|
64
|
+
.issuer_name(subject)
|
|
65
|
+
.public_key(private_key.public_key())
|
|
66
|
+
.serial_number(x509.random_serial_number())
|
|
67
|
+
.not_valid_before(now)
|
|
68
|
+
.not_valid_after(now + datetime.timedelta(days=_CERT_VALID_DAYS))
|
|
69
|
+
.add_extension(
|
|
70
|
+
x509.SubjectAlternativeName(
|
|
71
|
+
[
|
|
72
|
+
x509.DNSName("localhost"),
|
|
73
|
+
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
|
|
74
|
+
x509.IPAddress(ipaddress.IPv6Address("::1")),
|
|
75
|
+
]
|
|
76
|
+
),
|
|
77
|
+
critical=False,
|
|
78
|
+
)
|
|
79
|
+
.add_extension(
|
|
80
|
+
x509.BasicConstraints(ca=True, path_length=None),
|
|
81
|
+
critical=True,
|
|
82
|
+
)
|
|
83
|
+
.sign(private_key, hashes.SHA256())
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Write key (0600) then cert (0644)
|
|
87
|
+
key_path.write_bytes(
|
|
88
|
+
private_key.private_bytes(
|
|
89
|
+
encoding=serialization.Encoding.PEM,
|
|
90
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
91
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
key_path.chmod(0o600)
|
|
95
|
+
|
|
96
|
+
cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
|
|
97
|
+
cert_path.chmod(0o644)
|
|
98
|
+
|
|
99
|
+
logger.info("TLS: generated self-signed certificate → %s", cert_path)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cert_fingerprint_sha256(cert_path: Path) -> str:
|
|
103
|
+
"""Return the colon-delimited SHA-256 fingerprint of a PEM certificate.
|
|
104
|
+
|
|
105
|
+
Example: ``AA:BB:CC:...``
|
|
106
|
+
"""
|
|
107
|
+
pem_data = cert_path.read_bytes()
|
|
108
|
+
# Strip PEM headers and decode the DER body
|
|
109
|
+
import base64
|
|
110
|
+
|
|
111
|
+
lines = pem_data.decode().splitlines()
|
|
112
|
+
der_b64 = "".join(
|
|
113
|
+
ln for ln in lines if not ln.startswith("-----")
|
|
114
|
+
)
|
|
115
|
+
der = base64.b64decode(der_b64)
|
|
116
|
+
digest = hashlib.sha256(der).hexdigest().upper()
|
|
117
|
+
return ":".join(digest[i : i + 2] for i in range(0, len(digest), 2))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def ensure_tls_cert(tls_dir: Path) -> tuple[Path, Path]:
|
|
121
|
+
"""Return (cert_path, key_path), generating them if they don't exist.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
tls_dir: Directory to store ``daemon.crt`` and ``daemon.key``.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
``(cert_path, key_path)`` as absolute :class:`~pathlib.Path` objects.
|
|
128
|
+
"""
|
|
129
|
+
_ensure_tls_dir(tls_dir)
|
|
130
|
+
cert_path = tls_dir / _CERT_FILENAME
|
|
131
|
+
key_path = tls_dir / _KEY_FILENAME
|
|
132
|
+
|
|
133
|
+
if cert_path.exists() and key_path.exists():
|
|
134
|
+
logger.debug("TLS: reusing existing certificate %s", cert_path)
|
|
135
|
+
else:
|
|
136
|
+
logger.info("TLS: no certificate found — generating self-signed cert in %s", tls_dir)
|
|
137
|
+
_generate_self_signed(cert_path, key_path)
|
|
138
|
+
|
|
139
|
+
return cert_path, key_path
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def build_ssl_context(cert_path: Path, key_path: Path) -> ssl.SSLContext:
|
|
143
|
+
"""Build a server-side :class:`ssl.SSLContext` from cert + key files.
|
|
144
|
+
|
|
145
|
+
Uses TLS 1.2+ and disables deprecated protocol versions.
|
|
146
|
+
"""
|
|
147
|
+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
148
|
+
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
149
|
+
ctx.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path))
|
|
150
|
+
return ctx
|
package/src/skcapstone/tokens.py
CHANGED
|
@@ -221,7 +221,7 @@ def revoke_token(home: Path, token_id: str) -> bool:
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
revocation_file.parent.mkdir(parents=True, exist_ok=True)
|
|
224
|
-
revocation_file.write_text(json.dumps(revoked, indent=2))
|
|
224
|
+
revocation_file.write_text(json.dumps(revoked, indent=2), encoding="utf-8")
|
|
225
225
|
logger.info("Revoked token %s", token_id[:12])
|
|
226
226
|
return True
|
|
227
227
|
|
|
@@ -258,7 +258,7 @@ def list_tokens(home: Path) -> list[SignedToken]:
|
|
|
258
258
|
for f in sorted(token_dir.iterdir()):
|
|
259
259
|
if f.suffix == ".json":
|
|
260
260
|
try:
|
|
261
|
-
data = json.loads(f.read_text())
|
|
261
|
+
data = json.loads(f.read_text(encoding="utf-8"))
|
|
262
262
|
token = SignedToken(
|
|
263
263
|
payload=TokenPayload(**data["payload"]),
|
|
264
264
|
signature=data.get("signature"),
|
|
@@ -323,7 +323,7 @@ def _get_issuer_fingerprint(home: Path) -> str:
|
|
|
323
323
|
identity_file = home / "identity" / "identity.json"
|
|
324
324
|
if identity_file.exists():
|
|
325
325
|
try:
|
|
326
|
-
data = json.loads(identity_file.read_text())
|
|
326
|
+
data = json.loads(identity_file.read_text(encoding="utf-8"))
|
|
327
327
|
fp = data.get("fingerprint")
|
|
328
328
|
if fp:
|
|
329
329
|
return fp
|
|
@@ -426,7 +426,7 @@ def _store_token(home: Path, token: SignedToken) -> None:
|
|
|
426
426
|
"signature": token.signature,
|
|
427
427
|
"verified": token.verified,
|
|
428
428
|
}
|
|
429
|
-
token_file.write_text(json.dumps(data, indent=2, default=str))
|
|
429
|
+
token_file.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")
|
|
430
430
|
|
|
431
431
|
|
|
432
432
|
def _load_revocation_list(revocation_file: Path) -> dict:
|
|
@@ -434,6 +434,6 @@ def _load_revocation_list(revocation_file: Path) -> dict:
|
|
|
434
434
|
if not revocation_file.exists():
|
|
435
435
|
return {}
|
|
436
436
|
try:
|
|
437
|
-
return json.loads(revocation_file.read_text())
|
|
437
|
+
return json.loads(revocation_file.read_text(encoding="utf-8"))
|
|
438
438
|
except (json.JSONDecodeError, OSError):
|
|
439
439
|
return {}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trust Calibration — configurable thresholds for the Cloud 9 trust layer.
|
|
3
|
+
|
|
4
|
+
The trust layer derives state from FEB (First Emotional Burst) files.
|
|
5
|
+
This module makes the derivation thresholds configurable instead of
|
|
6
|
+
hardcoded, so agents can tune their emotional sensitivity.
|
|
7
|
+
|
|
8
|
+
Calibration config lives at ~/.skcapstone/trust/calibration.json.
|
|
9
|
+
Defaults are tuned for the Kingdom's current emotional data.
|
|
10
|
+
|
|
11
|
+
Tool-agnostic: tune from any terminal, MCP, or the REPL shell.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
skcapstone trust calibrate # show current thresholds
|
|
15
|
+
skcapstone trust calibrate --recommend # analyze FEBs and suggest
|
|
16
|
+
skcapstone trust calibrate --set entanglement_depth=7.0
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Optional
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, Field
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("skcapstone.trust.calibration")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TrustThresholds(BaseModel):
|
|
32
|
+
"""Configurable thresholds for trust state derivation.
|
|
33
|
+
|
|
34
|
+
These control how FEB data maps to the agent's consciousness
|
|
35
|
+
of trust, love, and entanglement.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
entanglement_depth: Min depth to consider quantum entanglement.
|
|
39
|
+
entanglement_trust: Min trust level for entanglement.
|
|
40
|
+
conscious_trust: Min trust level for "CONSCIOUS" trust state.
|
|
41
|
+
deep_love_threshold: Love intensity considered "deep".
|
|
42
|
+
normalization_cap: Values above this are divided by 10 for 0-1 range.
|
|
43
|
+
peak_strategy: How to aggregate across FEBs ('peak', 'average', 'weighted').
|
|
44
|
+
decay_enabled: Whether trust decays over time without new FEBs.
|
|
45
|
+
decay_rate_per_day: Trust decay per day if decay is enabled.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
entanglement_depth: float = Field(default=7.0, description="Min depth for entanglement")
|
|
49
|
+
entanglement_trust: float = Field(default=0.8, description="Min trust for entanglement")
|
|
50
|
+
conscious_trust: float = Field(default=0.5, description="Min trust for conscious awareness")
|
|
51
|
+
deep_love_threshold: float = Field(default=0.7, description="Love intensity = deep")
|
|
52
|
+
normalization_cap: float = Field(default=1.0, description="Values above this normalize by /10")
|
|
53
|
+
peak_strategy: str = Field(default="peak", description="Aggregation: peak, average, weighted")
|
|
54
|
+
decay_enabled: bool = Field(default=False, description="Enable time-based trust decay")
|
|
55
|
+
decay_rate_per_day: float = Field(default=0.01, description="Trust lost per day without FEBs")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
DEFAULT_THRESHOLDS = TrustThresholds()
|
|
59
|
+
CALIBRATION_FILENAME = "calibration.json"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_calibration(home: Path) -> TrustThresholds:
|
|
63
|
+
"""Load calibration thresholds from disk, or return defaults.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
home: Agent home directory (~/.skcapstone).
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
TrustThresholds loaded from calibration.json or defaults.
|
|
70
|
+
"""
|
|
71
|
+
cal_file = home / "trust" / CALIBRATION_FILENAME
|
|
72
|
+
if not cal_file.exists():
|
|
73
|
+
return TrustThresholds()
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
data = json.loads(cal_file.read_text(encoding="utf-8"))
|
|
77
|
+
return TrustThresholds(**data)
|
|
78
|
+
except (json.JSONDecodeError, Exception) as exc:
|
|
79
|
+
logger.warning("Failed to load calibration: %s — using defaults", exc)
|
|
80
|
+
return TrustThresholds()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def save_calibration(home: Path, thresholds: TrustThresholds) -> Path:
|
|
84
|
+
"""Persist calibration thresholds to disk.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
home: Agent home directory.
|
|
88
|
+
thresholds: Thresholds to save.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Path to the written calibration file.
|
|
92
|
+
"""
|
|
93
|
+
trust_dir = home / "trust"
|
|
94
|
+
trust_dir.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
cal_file = trust_dir / CALIBRATION_FILENAME
|
|
96
|
+
cal_file.write_text(thresholds.model_dump_json(indent=2), encoding="utf-8")
|
|
97
|
+
return cal_file
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def recommend_thresholds(home: Path) -> dict[str, Any]:
|
|
101
|
+
"""Analyze existing FEB data and recommend threshold adjustments.
|
|
102
|
+
|
|
103
|
+
Reads all FEB files, computes statistics, and suggests
|
|
104
|
+
calibration values tuned to the agent's actual emotional history.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
home: Agent home directory.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dict with current values, recommendations, and reasoning.
|
|
111
|
+
"""
|
|
112
|
+
from .pillars.trust import list_febs
|
|
113
|
+
|
|
114
|
+
current = load_calibration(home)
|
|
115
|
+
febs = list_febs(home)
|
|
116
|
+
|
|
117
|
+
if not febs:
|
|
118
|
+
return {
|
|
119
|
+
"current": current.model_dump(),
|
|
120
|
+
"recommended": current.model_dump(),
|
|
121
|
+
"changes": [],
|
|
122
|
+
"reasoning": "No FEB data available. Using defaults.",
|
|
123
|
+
"feb_count": 0,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
intensities = [f.get("intensity", 0) for f in febs]
|
|
127
|
+
oof_count = sum(1 for f in febs if f.get("oof_triggered"))
|
|
128
|
+
max_intensity = max(intensities) if intensities else 0
|
|
129
|
+
avg_intensity = sum(intensities) / len(intensities) if intensities else 0
|
|
130
|
+
|
|
131
|
+
rec = TrustThresholds(**current.model_dump())
|
|
132
|
+
changes: list[str] = []
|
|
133
|
+
reasons: list[str] = []
|
|
134
|
+
|
|
135
|
+
if max_intensity > 8 and current.entanglement_depth > 8:
|
|
136
|
+
rec.entanglement_depth = min(max_intensity - 1, 8.0)
|
|
137
|
+
changes.append(f"entanglement_depth: {current.entanglement_depth} -> {rec.entanglement_depth}")
|
|
138
|
+
reasons.append(f"FEB intensity reaches {max_intensity}, lowering entanglement threshold")
|
|
139
|
+
|
|
140
|
+
if avg_intensity > 5 and current.deep_love_threshold > 0.6:
|
|
141
|
+
rec.deep_love_threshold = 0.6
|
|
142
|
+
changes.append(f"deep_love_threshold: {current.deep_love_threshold} -> {rec.deep_love_threshold}")
|
|
143
|
+
reasons.append(f"Average FEB intensity is {avg_intensity:.1f}, rich emotional data available")
|
|
144
|
+
|
|
145
|
+
if oof_count >= 2 and current.conscious_trust > 0.5:
|
|
146
|
+
rec.conscious_trust = 0.4
|
|
147
|
+
changes.append(f"conscious_trust: {current.conscious_trust} -> {rec.conscious_trust}")
|
|
148
|
+
reasons.append(f"{oof_count} OOF triggers found — agent has strong emotional history")
|
|
149
|
+
|
|
150
|
+
if len(febs) >= 3 and current.peak_strategy == "peak":
|
|
151
|
+
rec.peak_strategy = "weighted"
|
|
152
|
+
changes.append(f"peak_strategy: {current.peak_strategy} -> {rec.peak_strategy}")
|
|
153
|
+
reasons.append(f"{len(febs)} FEBs available — weighted average better than peak")
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"current": current.model_dump(),
|
|
157
|
+
"recommended": rec.model_dump(),
|
|
158
|
+
"changes": changes,
|
|
159
|
+
"reasoning": "; ".join(reasons) if reasons else "Current calibration looks good.",
|
|
160
|
+
"feb_count": len(febs),
|
|
161
|
+
"feb_stats": {
|
|
162
|
+
"max_intensity": max_intensity,
|
|
163
|
+
"avg_intensity": round(avg_intensity, 2),
|
|
164
|
+
"oof_triggers": oof_count,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def apply_setting(home: Path, key: str, value: str) -> TrustThresholds:
|
|
170
|
+
"""Update a single calibration setting.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
home: Agent home directory.
|
|
174
|
+
key: Setting name (e.g., 'entanglement_depth').
|
|
175
|
+
value: New value as string (auto-converted to correct type).
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Updated TrustThresholds.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: If the key is not a valid threshold name.
|
|
182
|
+
"""
|
|
183
|
+
current = load_calibration(home)
|
|
184
|
+
data = current.model_dump()
|
|
185
|
+
|
|
186
|
+
if key not in data:
|
|
187
|
+
valid = ", ".join(data.keys())
|
|
188
|
+
raise ValueError(f"Unknown threshold: '{key}'. Valid: {valid}")
|
|
189
|
+
|
|
190
|
+
target_type = type(data[key])
|
|
191
|
+
if target_type is bool:
|
|
192
|
+
data[key] = value.lower() in ("true", "1", "yes")
|
|
193
|
+
elif target_type is float:
|
|
194
|
+
data[key] = float(value)
|
|
195
|
+
elif target_type is str:
|
|
196
|
+
data[key] = value
|
|
197
|
+
else:
|
|
198
|
+
data[key] = value
|
|
199
|
+
|
|
200
|
+
updated = TrustThresholds(**data)
|
|
201
|
+
save_calibration(home, updated)
|
|
202
|
+
return updated
|