@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.
Files changed (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,495 @@
1
+ """Tests for SKSecurity KMS — sovereign key management.
2
+
3
+ Tests the skcapstone KMS wrapper which delegates crypto operations
4
+ to sksecurity.kms (AES-256-GCM key wrapping, HKDF-SHA256 derivation).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ from skcapstone.kms import (
16
+ KeyRecord,
17
+ KeyStatus,
18
+ KeyStore,
19
+ KeyType,
20
+ RotationEntry,
21
+ _decrypt_at_rest,
22
+ _derive_key,
23
+ _encrypt_at_rest,
24
+ _key_fingerprint,
25
+ _key_id,
26
+ )
27
+
28
+
29
+ @pytest.fixture
30
+ def home(tmp_path: Path) -> Path:
31
+ """Create a minimal agent home for KMS tests."""
32
+ identity_dir = tmp_path / "identity"
33
+ identity_dir.mkdir()
34
+ manifest = {
35
+ "name": "test-agent",
36
+ "email": "test@skcapstone.local",
37
+ "fingerprint": "ABCD1234567890ABCDEF1234567890ABCDEF1234",
38
+ "capauth_managed": False,
39
+ }
40
+ (identity_dir / "identity.json").write_text(json.dumps(manifest), encoding="utf-8")
41
+
42
+ security_dir = tmp_path / "security"
43
+ security_dir.mkdir()
44
+
45
+ return tmp_path
46
+
47
+
48
+ @pytest.fixture
49
+ def store(home: Path) -> KeyStore:
50
+ """Create and initialize a KeyStore."""
51
+ ks = KeyStore(home)
52
+ ks.initialize()
53
+ return ks
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Crypto helper tests
58
+ # ---------------------------------------------------------------------------
59
+
60
+
61
+ class TestCryptoHelpers:
62
+ """Tests for low-level cryptographic helpers."""
63
+
64
+ def test_derive_key_deterministic(self) -> None:
65
+ """Same inputs produce same output."""
66
+ k1 = _derive_key(b"master", b"info")
67
+ k2 = _derive_key(b"master", b"info")
68
+ assert k1 == k2
69
+
70
+ def test_derive_key_different_info(self) -> None:
71
+ """Different info strings produce different keys."""
72
+ k1 = _derive_key(b"master", b"service-a")
73
+ k2 = _derive_key(b"master", b"service-b")
74
+ assert k1 != k2
75
+
76
+ def test_derive_key_length(self) -> None:
77
+ """Output length matches requested."""
78
+ k16 = _derive_key(b"master", b"info", length=16)
79
+ k64 = _derive_key(b"master", b"info", length=64)
80
+ assert len(k16) == 16
81
+ assert len(k64) == 64
82
+
83
+ def test_aes_gcm_roundtrip(self) -> None:
84
+ """Encrypt then decrypt returns original plaintext."""
85
+ key = _derive_key(b"test", b"enc", length=32)
86
+ plaintext = b"sovereign secrets"
87
+ ct = _encrypt_at_rest(plaintext, key)
88
+ assert _decrypt_at_rest(ct, key) == plaintext
89
+
90
+ def test_aes_gcm_ciphertext_format(self) -> None:
91
+ """AES-256-GCM output is nonce (12) + ciphertext + tag (16)."""
92
+ key = os.urandom(32)
93
+ plaintext = b"test data"
94
+ ct = _encrypt_at_rest(plaintext, key)
95
+ # nonce=12, plaintext_len=9, tag=16 → total=37
96
+ assert len(ct) == 12 + len(plaintext) + 16
97
+
98
+ def test_aes_gcm_wrong_key_fails(self) -> None:
99
+ """Decrypting with wrong key raises an error."""
100
+ key1 = _derive_key(b"key1", b"enc", length=32)
101
+ key2 = _derive_key(b"key2", b"enc", length=32)
102
+ ct = _encrypt_at_rest(b"data", key1)
103
+ with pytest.raises(Exception):
104
+ _decrypt_at_rest(ct, key2)
105
+
106
+ def test_key_fingerprint_deterministic(self) -> None:
107
+ """Same material produces same fingerprint."""
108
+ fp1 = _key_fingerprint(b"material")
109
+ fp2 = _key_fingerprint(b"material")
110
+ assert fp1 == fp2
111
+ assert len(fp1) == 64 # SHA-256 hex
112
+
113
+ def test_key_id_deterministic(self) -> None:
114
+ """Same inputs produce same key_id."""
115
+ id1 = _key_id("label", KeyType.SERVICE)
116
+ id2 = _key_id("label", KeyType.SERVICE)
117
+ assert id1 == id2
118
+ assert len(id1) == 16
119
+
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # KeyStore initialization
123
+ # ---------------------------------------------------------------------------
124
+
125
+
126
+ class TestKeyStoreInit:
127
+ """Tests for KMS initialization."""
128
+
129
+ def test_initialize_creates_master(self, home: Path) -> None:
130
+ """Initialize creates a master key."""
131
+ store = KeyStore(home)
132
+ master = store.initialize()
133
+ assert master.key_type == KeyType.MASTER
134
+ assert master.status == KeyStatus.ACTIVE
135
+ assert master.label == "master"
136
+
137
+ def test_initialize_idempotent(self, store: KeyStore) -> None:
138
+ """Calling initialize twice returns same master."""
139
+ m1 = store.initialize()
140
+ m2 = store.initialize()
141
+ assert m1.key_id == m2.key_id
142
+ assert m1.fingerprint == m2.fingerprint
143
+
144
+ def test_initialize_creates_directory_structure(self, home: Path) -> None:
145
+ """KMS directories are created on init."""
146
+ KeyStore(home).initialize()
147
+ assert (home / "security" / "kms").is_dir()
148
+ assert (home / "security" / "kms" / "keys").is_dir()
149
+ assert (home / "security" / "kms" / "keystore.json").exists()
150
+
151
+ def test_initialize_without_identity(self, tmp_path: Path) -> None:
152
+ """KMS works with random seed when no identity exists."""
153
+ store = KeyStore(tmp_path)
154
+ master = store.initialize()
155
+ assert master.key_type == KeyType.MASTER
156
+ assert master.status == KeyStatus.ACTIVE
157
+
158
+ def test_algorithm_uses_aes_256_gcm(self, store: KeyStore) -> None:
159
+ """Default algorithm is HKDF-SHA256+AES-256-GCM."""
160
+ key = store.derive_service_key("test-algo")
161
+ assert "AES-256-GCM" in key.algorithm
162
+
163
+
164
+ # ---------------------------------------------------------------------------
165
+ # Service key derivation
166
+ # ---------------------------------------------------------------------------
167
+
168
+
169
+ class TestServiceKeys:
170
+ """Tests for service key derivation."""
171
+
172
+ def test_derive_service_key(self, store: KeyStore) -> None:
173
+ """Derive a service key from master."""
174
+ key = store.derive_service_key("api-gateway")
175
+ assert key.key_type == KeyType.SERVICE
176
+ assert key.label == "api-gateway"
177
+ assert key.status == KeyStatus.ACTIVE
178
+ assert key.parent_key_id is not None
179
+
180
+ def test_service_key_idempotent(self, store: KeyStore) -> None:
181
+ """Same service name returns existing key."""
182
+ k1 = store.derive_service_key("skchat")
183
+ k2 = store.derive_service_key("skchat")
184
+ assert k1.key_id == k2.key_id
185
+
186
+ def test_different_services_different_keys(self, store: KeyStore) -> None:
187
+ """Different services produce different keys."""
188
+ k1 = store.derive_service_key("service-a")
189
+ k2 = store.derive_service_key("service-b")
190
+ assert k1.fingerprint != k2.fingerprint
191
+
192
+ def test_service_key_with_ttl(self, store: KeyStore) -> None:
193
+ """Service key with TTL has expiry set."""
194
+ key = store.derive_service_key("temp-service", ttl_days=30)
195
+ assert key.expires_at is not None
196
+
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # Subkey derivation
200
+ # ---------------------------------------------------------------------------
201
+
202
+
203
+ class TestSubkeys:
204
+ """Tests for subkey derivation."""
205
+
206
+ def test_derive_subkey_from_master(self, store: KeyStore) -> None:
207
+ """Derive a subkey from the master key."""
208
+ key = store.derive_subkey("delegation-a")
209
+ assert key.key_type == KeyType.SUBKEY
210
+ assert key.label == "delegation-a"
211
+
212
+ def test_derive_subkey_from_service(self, store: KeyStore) -> None:
213
+ """Derive a subkey from a service key."""
214
+ store.derive_service_key("parent-service")
215
+ key = store.derive_subkey("child", parent_label="parent-service")
216
+ assert key.key_type == KeyType.SUBKEY
217
+ assert key.parent_key_id is not None
218
+
219
+ def test_subkey_from_missing_parent_raises(self, store: KeyStore) -> None:
220
+ """Deriving from a nonexistent parent raises ValueError."""
221
+ with pytest.raises(ValueError, match="not found"):
222
+ store.derive_subkey("orphan", parent_label="nonexistent")
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Team key management
227
+ # ---------------------------------------------------------------------------
228
+
229
+
230
+ class TestTeamKeys:
231
+ """Tests for team key creation and member management."""
232
+
233
+ def test_create_team_key(self, store: KeyStore) -> None:
234
+ """Create a team key with members."""
235
+ key = store.create_team_key("dev-team", members=["opus", "lumina"])
236
+ assert key.key_type == KeyType.TEAM
237
+ assert key.label == "dev-team"
238
+ assert key.members == ["opus", "lumina"]
239
+
240
+ def test_team_key_idempotent(self, store: KeyStore) -> None:
241
+ """Same team name returns existing key."""
242
+ k1 = store.create_team_key("team-x")
243
+ k2 = store.create_team_key("team-x")
244
+ assert k1.key_id == k2.key_id
245
+
246
+ def test_add_member(self, store: KeyStore) -> None:
247
+ """Add a member to a team key."""
248
+ store.create_team_key("team-y", members=["opus"])
249
+ updated = store.add_team_member("team-y", "grok")
250
+ assert "grok" in updated.members
251
+
252
+ def test_add_duplicate_member_noop(self, store: KeyStore) -> None:
253
+ """Adding an existing member is a no-op."""
254
+ store.create_team_key("team-z", members=["opus"])
255
+ updated = store.add_team_member("team-z", "opus")
256
+ assert updated.members.count("opus") == 1
257
+
258
+ def test_remove_member(self, store: KeyStore) -> None:
259
+ """Remove a member from a team key."""
260
+ store.create_team_key("team-w", members=["opus", "lumina", "grok"])
261
+ updated = store.remove_team_member("team-w", "lumina")
262
+ assert "lumina" not in updated.members
263
+ assert "opus" in updated.members
264
+
265
+ def test_add_member_missing_team_raises(self, store: KeyStore) -> None:
266
+ """Adding to a nonexistent team raises ValueError."""
267
+ with pytest.raises(ValueError, match="not found"):
268
+ store.add_team_member("ghost-team", "opus")
269
+
270
+ def test_team_acl_grants_access(self, store: KeyStore) -> None:
271
+ """Team member can access key material."""
272
+ key = store.create_team_key("acl-team", members=["opus"])
273
+ material = store.get_key_material(key.key_id, agent_name="opus")
274
+ assert len(material) == 32
275
+
276
+ def test_team_acl_denies_access(self, store: KeyStore) -> None:
277
+ """Non-member is denied access to team key material."""
278
+ key = store.create_team_key("private-team", members=["opus"])
279
+ with pytest.raises(PermissionError, match="not in team"):
280
+ store.get_key_material(key.key_id, agent_name="intruder")
281
+
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # Key rotation
285
+ # ---------------------------------------------------------------------------
286
+
287
+
288
+ class TestKeyRotation:
289
+ """Tests for key rotation."""
290
+
291
+ def test_rotate_service_key(self, store: KeyStore) -> None:
292
+ """Rotating a service key produces a new version."""
293
+ old = store.derive_service_key("rotating-svc")
294
+ new = store.rotate_key(old.key_id, reason="scheduled")
295
+ assert new.version == old.version + 1
296
+ assert new.fingerprint != old.fingerprint
297
+ assert new.status == KeyStatus.ACTIVE
298
+
299
+ def test_old_key_marked_rotated(self, store: KeyStore) -> None:
300
+ """The old key is marked as ROTATED after rotation."""
301
+ old = store.derive_service_key("rotate-me")
302
+ old_id = old.key_id
303
+ store.rotate_key(old_id)
304
+
305
+ records = store.list_keys(include_inactive=True)
306
+ old_record = next(r for r in records if r.key_id == old_id)
307
+ assert old_record.status == KeyStatus.ROTATED
308
+ assert old_record.rotated_at is not None
309
+
310
+ def test_rotate_team_key_preserves_members(self, store: KeyStore) -> None:
311
+ """Team key rotation preserves the member list."""
312
+ old = store.create_team_key("team-rotate", members=["opus", "grok"])
313
+ new = store.rotate_key(old.key_id)
314
+ assert new.members == ["opus", "grok"]
315
+
316
+ def test_rotation_log_written(self, store: KeyStore, home: Path) -> None:
317
+ """Rotation events are logged."""
318
+ old = store.derive_service_key("log-svc")
319
+ store.rotate_key(old.key_id)
320
+
321
+ log_file = home / "security" / "kms" / "rotation-log.json"
322
+ assert log_file.exists()
323
+ log = json.loads(log_file.read_text(encoding="utf-8"))
324
+ assert len(log) >= 1
325
+ assert log[-1]["old_version"] == 1
326
+ assert log[-1]["new_version"] == 2
327
+
328
+ def test_rotate_master_key(self, store: KeyStore) -> None:
329
+ """Master key can be rotated."""
330
+ keys = store.list_keys(key_type=KeyType.MASTER)
331
+ master = keys[0]
332
+ new = store.rotate_key(master.key_id, reason="security audit")
333
+ assert new.version == master.version + 1
334
+
335
+ def test_rotate_missing_key_raises(self, store: KeyStore) -> None:
336
+ """Rotating a nonexistent key raises ValueError."""
337
+ with pytest.raises(ValueError, match="not found"):
338
+ store.rotate_key("nonexistent-id")
339
+
340
+
341
+ # ---------------------------------------------------------------------------
342
+ # Key revocation
343
+ # ---------------------------------------------------------------------------
344
+
345
+
346
+ class TestKeyRevocation:
347
+ """Tests for key revocation."""
348
+
349
+ def test_revoke_key(self, store: KeyStore) -> None:
350
+ """Revoking a key marks it and removes material."""
351
+ key = store.derive_service_key("revoke-me")
352
+ revoked = store.revoke_key(key.key_id)
353
+ assert revoked.status == KeyStatus.REVOKED
354
+
355
+ def test_revoked_key_material_deleted(self, store: KeyStore, home: Path) -> None:
356
+ """Key material is deleted on revocation."""
357
+ key = store.derive_service_key("delete-material")
358
+ key_file = home / "security" / "kms" / "keys" / f"{key.key_id}.key.enc"
359
+ assert key_file.exists()
360
+ store.revoke_key(key.key_id)
361
+ assert not key_file.exists()
362
+
363
+ def test_revoked_key_not_in_active_list(self, store: KeyStore) -> None:
364
+ """Revoked keys don't appear in active listing."""
365
+ key = store.derive_service_key("hidden")
366
+ store.revoke_key(key.key_id)
367
+ active = store.list_keys()
368
+ assert all(r.key_id != key.key_id for r in active)
369
+
370
+
371
+ # ---------------------------------------------------------------------------
372
+ # Listing and status
373
+ # ---------------------------------------------------------------------------
374
+
375
+
376
+ class TestListingAndStatus:
377
+ """Tests for key listing and status reporting."""
378
+
379
+ def test_list_keys_by_type(self, store: KeyStore) -> None:
380
+ """Filter keys by type."""
381
+ store.derive_service_key("svc-1")
382
+ store.create_team_key("team-1")
383
+ services = store.list_keys(key_type=KeyType.SERVICE)
384
+ teams = store.list_keys(key_type=KeyType.TEAM)
385
+ assert all(r.key_type == KeyType.SERVICE for r in services)
386
+ assert all(r.key_type == KeyType.TEAM for r in teams)
387
+
388
+ def test_status_summary(self, store: KeyStore) -> None:
389
+ """Status returns structured summary."""
390
+ store.derive_service_key("s1")
391
+ store.create_team_key("t1", members=["opus"])
392
+ status = store.status()
393
+ assert status["initialized"] is True
394
+ assert status["active"] >= 3 # master + service + team
395
+ assert "service" in status["by_type"]
396
+ assert "team" in status["by_type"]
397
+ assert "backend_available" in status
398
+
399
+ def test_get_key_material(self, store: KeyStore) -> None:
400
+ """Raw key material can be retrieved."""
401
+ key = store.derive_service_key("get-material")
402
+ material = store.get_key_material(key.key_id)
403
+ assert len(material) == 32
404
+
405
+ def test_get_key_material_revoked_raises(self, store: KeyStore) -> None:
406
+ """Cannot get material for revoked key."""
407
+ key = store.derive_service_key("revoked-access")
408
+ store.revoke_key(key.key_id)
409
+ with pytest.raises(ValueError, match="revoked"):
410
+ store.get_key_material(key.key_id)
411
+
412
+
413
+ # ---------------------------------------------------------------------------
414
+ # Backend integration tests
415
+ # ---------------------------------------------------------------------------
416
+
417
+
418
+ class TestBackendIntegration:
419
+ """Tests for sksecurity KMS backend integration."""
420
+
421
+ def test_backend_property_available(self, store: KeyStore) -> None:
422
+ """Backend property returns KMS when sksecurity is installed."""
423
+ from skcapstone.kms import _HAS_BACKEND
424
+ if _HAS_BACKEND:
425
+ assert store.backend is not None
426
+ assert store.backend.is_unsealed
427
+ else:
428
+ assert store.backend is None
429
+
430
+ def test_status_reports_backend(self, store: KeyStore) -> None:
431
+ """Status includes backend availability information."""
432
+ status = store.status()
433
+ assert "backend_available" in status
434
+ assert "backend_unsealed" in status
435
+
436
+ def test_backend_4_tier_operations(self, store: KeyStore) -> None:
437
+ """When backend is available, 4-tier operations work."""
438
+ from skcapstone.kms import _HAS_BACKEND
439
+ if not _HAS_BACKEND:
440
+ pytest.skip("sksecurity not installed")
441
+
442
+ backend = store.backend
443
+ team_key = backend.create_team_key("backend-team")
444
+ assert team_key.team_id == "backend-team"
445
+
446
+ agent_key = backend.create_agent_key("backend-team", "agent-01")
447
+ assert agent_key.agent_id == "agent-01"
448
+
449
+ dek = backend.create_dek("backend-team", "agent-01", purpose="test")
450
+ raw_dek = backend.unwrap_dek(dek.key_id)
451
+ assert len(raw_dek) == 32
452
+
453
+
454
+ # ---------------------------------------------------------------------------
455
+ # Model tests
456
+ # ---------------------------------------------------------------------------
457
+
458
+
459
+ class TestModels:
460
+ """Tests for Pydantic models."""
461
+
462
+ def test_key_record_defaults(self) -> None:
463
+ """KeyRecord has sensible defaults."""
464
+ record = KeyRecord(
465
+ key_id="test123",
466
+ key_type=KeyType.SERVICE,
467
+ label="test",
468
+ fingerprint="abc" * 20,
469
+ )
470
+ assert record.status == KeyStatus.ACTIVE
471
+ assert record.version == 1
472
+ assert record.members == []
473
+
474
+ def test_key_record_default_algorithm(self) -> None:
475
+ """KeyRecord default algorithm is AES-256-GCM based."""
476
+ record = KeyRecord(
477
+ key_id="test",
478
+ key_type=KeyType.SERVICE,
479
+ label="test",
480
+ fingerprint="abc",
481
+ )
482
+ assert "AES-256-GCM" in record.algorithm
483
+
484
+ def test_rotation_entry_serializes(self) -> None:
485
+ """RotationEntry can be serialized to JSON."""
486
+ entry = RotationEntry(
487
+ key_id="k1",
488
+ old_fingerprint="old",
489
+ new_fingerprint="new",
490
+ old_version=1,
491
+ new_version=2,
492
+ )
493
+ data = entry.model_dump(mode="json")
494
+ assert data["old_version"] == 1
495
+ assert data["new_version"] == 2