kirby-api 0.1.0__tar.gz

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 (255) hide show
  1. kirby_api-0.1.0/LICENSE +170 -0
  2. kirby_api-0.1.0/PKG-INFO +273 -0
  3. kirby_api-0.1.0/README.md +223 -0
  4. kirby_api-0.1.0/kirby/__init__.py +13 -0
  5. kirby_api-0.1.0/kirby/_migrations/README +1 -0
  6. kirby_api-0.1.0/kirby/_migrations/alembic.ini +149 -0
  7. kirby_api-0.1.0/kirby/_migrations/env.py +74 -0
  8. kirby_api-0.1.0/kirby/_migrations/script.py.mako +28 -0
  9. kirby_api-0.1.0/kirby/_migrations/versions/1b7affd8668e_initial_schema_users_tenants_characters_.py +129 -0
  10. kirby_api-0.1.0/kirby/_migrations/versions/20260424_0001_combat_tables.py +197 -0
  11. kirby_api-0.1.0/kirby/_migrations/versions/20260429_0001_add_combatant_jsonb.py +43 -0
  12. kirby_api-0.1.0/kirby/_migrations/versions/20260501_0001_codex_targeted_pages.py +54 -0
  13. kirby_api-0.1.0/kirby/_migrations/versions/20260501_0002_hd_relational_schema.py +652 -0
  14. kirby_api-0.1.0/kirby/_migrations/versions/20260501_0003_definitions_codex_chunk_id.py +74 -0
  15. kirby_api-0.1.0/kirby/_migrations/versions/20260503_0001_per_template_base_values.py +100 -0
  16. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0001_pr1_relational_combat_schema.py +553 -0
  17. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0002_seed_tactics_and_profiles.py +274 -0
  18. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0003_backfill_combat_relational.py +410 -0
  19. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0004_drop_combat_jsonb_columns.py +84 -0
  20. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0005_pr1_index_audit_fills.py +127 -0
  21. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0006_pending_phase_modifiers.py +67 -0
  22. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0007_combat_session_scene_id.py +41 -0
  23. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0008_morale_expires_at_seq.py +37 -0
  24. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0009_is_stunned.py +35 -0
  25. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0010_tactic_catalog_expansion.py +225 -0
  26. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0011_slam_velocity_tactic.py +118 -0
  27. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0012_mind_control_state.py +45 -0
  28. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0013_mental_illusion_state.py +41 -0
  29. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0014_mental_entangle_state.py +37 -0
  30. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0015_was_surprised.py +46 -0
  31. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0016_catalog_expansion_for_new_actions.py +203 -0
  32. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0017_combat_started.py +37 -0
  33. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0018_physical_entangle.py +34 -0
  34. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0019_stat_drains.py +45 -0
  35. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0020_continuous_effects.py +48 -0
  36. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0021_lockout_state.py +39 -0
  37. kirby_api-0.1.0/kirby/_migrations/versions/20260504_0022_dispelled_powers.py +44 -0
  38. kirby_api-0.1.0/kirby/_migrations/versions/20260506_0001_campaign_management_v1.py +426 -0
  39. kirby_api-0.1.0/kirby/_migrations/versions/20260506_0002_extend_characters_for_pc.py +96 -0
  40. kirby_api-0.1.0/kirby/_migrations/versions/20260506_0003_combatant_control_mode.py +55 -0
  41. kirby_api-0.1.0/kirby/_migrations/versions/20260506_0004_brain_embeddings.py +71 -0
  42. kirby_api-0.1.0/kirby/_migrations/versions/20260507_0001_knowledge_tier.py +395 -0
  43. kirby_api-0.1.0/kirby/_migrations/versions/20260507_0002_normalize_jsonb.py +382 -0
  44. kirby_api-0.1.0/kirby/_migrations/versions/20260507_0003_drop_legacy_jsonb.py +180 -0
  45. kirby_api-0.1.0/kirby/_migrations/versions/20260507_0004_canon_embeddings.py +62 -0
  46. kirby_api-0.1.0/kirby/_migrations/versions/20260508_0001_comic_canon_init.py +253 -0
  47. kirby_api-0.1.0/kirby/_migrations/versions/20260511_0001_power_library_init.py +110 -0
  48. kirby_api-0.1.0/kirby/_migrations/versions/20260514_0001_combat_session_ai_provider.py +57 -0
  49. kirby_api-0.1.0/kirby/_migrations/versions/20260528_0001_coordinate_raw_tables.py +148 -0
  50. kirby_api-0.1.0/kirby/_migrations/versions/20260528_0002_coordination_initiator.py +46 -0
  51. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0001_character_def_rename.py +28 -0
  52. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0002_character_instance.py +53 -0
  53. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0003_team_def.py +55 -0
  54. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0004_team_member_def.py +40 -0
  55. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0005_team_instance.py +56 -0
  56. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0006_team_def_real_history.py +36 -0
  57. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0007_team_page.py +73 -0
  58. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0008_team_narrative.py +57 -0
  59. kirby_api-0.1.0/kirby/_migrations/versions/20260529_0009_migrate_org_journal_entries_to_team.py +102 -0
  60. kirby_api-0.1.0/kirby/_migrations/versions/20260530_0002_templates_catalog_columns.py +61 -0
  61. kirby_api-0.1.0/kirby/_migrations/versions/20260530_0003_reconcile_template_category_vocab.py +50 -0
  62. kirby_api-0.1.0/kirby/_migrations/versions/20260531_0001_align_catalog_to_engine_schema.py +44 -0
  63. kirby_api-0.1.0/kirby/_migrations/versions/20260601_0001_build_doc_fields.py +66 -0
  64. kirby_api-0.1.0/kirby/_migrations/versions/20260605_0001_proposals.py +53 -0
  65. kirby_api-0.1.0/kirby/_migrations/versions/20260605_0002_membership_roster.py +62 -0
  66. kirby_api-0.1.0/kirby/_migrations/versions/20260605_0003_xp_award.py +56 -0
  67. kirby_api-0.1.0/kirby/_migrations/versions/20260606_0001_base_drift_guard.py +31 -0
  68. kirby_api-0.1.0/kirby/_migrations/versions/20260607_0001_maneuver_details.py +50 -0
  69. kirby_api-0.1.0/kirby/_migrations/versions/20260609_0001_scene_wall_def_value.py +19 -0
  70. kirby_api-0.1.0/kirby/_migrations/versions/20260609_0002_combat_session_constructs.py +67 -0
  71. kirby_api-0.1.0/kirby/_migrations/versions/20260611_0001_arena_hazard_kind_status_tokens.py +32 -0
  72. kirby_api-0.1.0/kirby/_migrations/versions/20260611_0002_tactic_entry_role.py +28 -0
  73. kirby_api-0.1.0/kirby/_migrations/versions/20260611_0003_seed_archetype_profiles.py +596 -0
  74. kirby_api-0.1.0/kirby/_migrations/versions/20260612_0001_combatant_held_construct.py +29 -0
  75. kirby_api-0.1.0/kirby/_migrations/versions/20260613_0001_combatant_is_hidden.py +27 -0
  76. kirby_api-0.1.0/kirby/_migrations/versions/20260613_0002_combatant_flashed_sense.py +36 -0
  77. kirby_api-0.1.0/kirby/_migrations/versions/20260614_0001_construct_sense_group.py +35 -0
  78. kirby_api-0.1.0/kirby/_migrations/versions/20260614_0002_image_decoy.py +55 -0
  79. kirby_api-0.1.0/kirby/_migrations/versions/20260618_0001_character_extraction_tables.py +74 -0
  80. kirby_api-0.1.0/kirby/_migrations/versions/20260621_0001_combat_session_combatant_slot.py +51 -0
  81. kirby_api-0.1.0/kirby/_migrations/versions/20260621_0002_vpp_power.py +50 -0
  82. kirby_api-0.1.0/kirby/_migrations/versions/20260623_0001_power_categories.py +55 -0
  83. kirby_api-0.1.0/kirby/_migrations/versions/20260726_0001_combat_intent.py +64 -0
  84. kirby_api-0.1.0/kirby/_migrations/versions/20260727_0001_intent_checks_since_form.py +30 -0
  85. kirby_api-0.1.0/kirby/_migrations/versions/20260728_0001_stunned_at_sequence.py +31 -0
  86. kirby_api-0.1.0/kirby/_migrations/versions/20260729_0001_intent_budget_turns.py +20 -0
  87. kirby_api-0.1.0/kirby/_migrations/versions/20260730_0001_standable_structures.py +32 -0
  88. kirby_api-0.1.0/kirby/_migrations/versions/20260731_0001_climb_difficulty.py +25 -0
  89. kirby_api-0.1.0/kirby/_migrations/versions/20260731_0002_widen_character_object_alias.py +60 -0
  90. kirby_api-0.1.0/kirby/_migrations/versions/20260802_0001_identity_by_id_not_xmlid.py +76 -0
  91. kirby_api-0.1.0/kirby/_migrations/versions/20260803_0001_combatant_opening_state.py +89 -0
  92. kirby_api-0.1.0/kirby/_migrations/versions/20260804_0001_combat_random_fight.py +59 -0
  93. kirby_api-0.1.0/kirby/_migrations/versions/20260804_0002_presence_attack_effects.py +69 -0
  94. kirby_api-0.1.0/kirby/_migrations/versions/20260816_0001_template_level_power.py +52 -0
  95. kirby_api-0.1.0/kirby/_migrations/versions/20260831_0001_construct_no_teleport.py +30 -0
  96. kirby_api-0.1.0/kirby/_migrations/versions/20260831_0002_physical_entangle_state.py +32 -0
  97. kirby_api-0.1.0/kirby/_migrations/versions/20260831_0003_character_object_text.py +25 -0
  98. kirby_api-0.1.0/kirby/_migrations/versions/20260902_0001_combatant_in_hero_id.py +53 -0
  99. kirby_api-0.1.0/kirby/_migrations/versions/20260902_0002_object_affects_flags.py +51 -0
  100. kirby_api-0.1.0/kirby/_migrations/versions/20260917_0001_session_names_a_seat.py +84 -0
  101. kirby_api-0.1.0/kirby/_migrations/versions/20260918_0000_drop_combat_tables.py +85 -0
  102. kirby_api-0.1.0/kirby/_migrations/versions/20260918_0001_harness_tables.py +231 -0
  103. kirby_api-0.1.0/kirby/_migrations/versions/20260918_0002_narration_speaker_id.py +40 -0
  104. kirby_api-0.1.0/kirby/_migrations/versions/21980c836860_add_asset_id_refs.py +45 -0
  105. kirby_api-0.1.0/kirby/_migrations/versions/ba28e71c955d_codex_init.py +137 -0
  106. kirby_api-0.1.0/kirby/admin/__init__.py +1 -0
  107. kirby_api-0.1.0/kirby/auth/__init__.py +1 -0
  108. kirby_api-0.1.0/kirby/auth/authz.py +8 -0
  109. kirby_api-0.1.0/kirby/auth/dependencies.py +17 -0
  110. kirby_api-0.1.0/kirby/auth/jwt.py +232 -0
  111. kirby_api-0.1.0/kirby/auth/models.py +32 -0
  112. kirby_api-0.1.0/kirby/auth/routes.py +30 -0
  113. kirby_api-0.1.0/kirby/auth/users.py +55 -0
  114. kirby_api-0.1.0/kirby/builds/__init__.py +9 -0
  115. kirby_api-0.1.0/kirby/builds/build_doc_store.py +384 -0
  116. kirby_api-0.1.0/kirby/builds/category_extraction.py +83 -0
  117. kirby_api-0.1.0/kirby/builds/cli.py +746 -0
  118. kirby_api-0.1.0/kirby/builds/codex_linker.py +199 -0
  119. kirby_api-0.1.0/kirby/builds/combatant_loader.py +180 -0
  120. kirby_api-0.1.0/kirby/builds/concept_to_build.py +505 -0
  121. kirby_api-0.1.0/kirby/builds/hdc_exporter.py +107 -0
  122. kirby_api-0.1.0/kirby/builds/importer.py +577 -0
  123. kirby_api-0.1.0/kirby/builds/json_exporter.py +277 -0
  124. kirby_api-0.1.0/kirby/builds/json_importer.py +553 -0
  125. kirby_api-0.1.0/kirby/builds/models.py +842 -0
  126. kirby_api-0.1.0/kirby/builds/power_categories.py +164 -0
  127. kirby_api-0.1.0/kirby/builds/power_library_importer.py +625 -0
  128. kirby_api-0.1.0/kirby/builds/power_library_service.py +258 -0
  129. kirby_api-0.1.0/kirby/builds/recompute.py +37 -0
  130. kirby_api-0.1.0/kirby/builds/routes.py +330 -0
  131. kirby_api-0.1.0/kirby/builds/template_provider.py +249 -0
  132. kirby_api-0.1.0/kirby/campaigns/__init__.py +1 -0
  133. kirby_api-0.1.0/kirby/campaigns/_shared.py +36 -0
  134. kirby_api-0.1.0/kirby/campaigns/membership_service.py +64 -0
  135. kirby_api-0.1.0/kirby/campaigns/models.py +1153 -0
  136. kirby_api-0.1.0/kirby/campaigns/routes.py +306 -0
  137. kirby_api-0.1.0/kirby/campaigns/routes_acl.py +232 -0
  138. kirby_api-0.1.0/kirby/campaigns/routes_brain.py +392 -0
  139. kirby_api-0.1.0/kirby/campaigns/routes_documents.py +843 -0
  140. kirby_api-0.1.0/kirby/campaigns/routes_knowledge.py +711 -0
  141. kirby_api-0.1.0/kirby/campaigns/routes_membership.py +71 -0
  142. kirby_api-0.1.0/kirby/campaigns/routes_relationships.py +375 -0
  143. kirby_api-0.1.0/kirby/campaigns/routes_roster.py +122 -0
  144. kirby_api-0.1.0/kirby/campaigns/routes_sessions_events.py +448 -0
  145. kirby_api-0.1.0/kirby/campaigns/routes_xp.py +84 -0
  146. kirby_api-0.1.0/kirby/campaigns/services/__init__.py +0 -0
  147. kirby_api-0.1.0/kirby/campaigns/services/brain.py +610 -0
  148. kirby_api-0.1.0/kirby/campaigns/services/conflicts.py +501 -0
  149. kirby_api-0.1.0/kirby/campaigns/services/embeddings.py +389 -0
  150. kirby_api-0.1.0/kirby/campaigns/services/extract.py +234 -0
  151. kirby_api-0.1.0/kirby/campaigns/services/generation.py +317 -0
  152. kirby_api-0.1.0/kirby/campaigns/services/legacy_translation.py +208 -0
  153. kirby_api-0.1.0/kirby/campaigns/services/permissions.py +266 -0
  154. kirby_api-0.1.0/kirby/campaigns/services/recap.py +158 -0
  155. kirby_api-0.1.0/kirby/characters/__init__.py +1 -0
  156. kirby_api-0.1.0/kirby/characters/_shared.py +18 -0
  157. kirby_api-0.1.0/kirby/characters/build_routes.py +95 -0
  158. kirby_api-0.1.0/kirby/characters/combat_seed.py +18 -0
  159. kirby_api-0.1.0/kirby/characters/corpus_copy.py +353 -0
  160. kirby_api-0.1.0/kirby/characters/models.py +427 -0
  161. kirby_api-0.1.0/kirby/characters/proposals/__init__.py +0 -0
  162. kirby_api-0.1.0/kirby/characters/proposals/routes.py +193 -0
  163. kirby_api-0.1.0/kirby/characters/proposals/service.py +112 -0
  164. kirby_api-0.1.0/kirby/characters/routes.py +574 -0
  165. kirby_api-0.1.0/kirby/characters/services/__init__.py +0 -0
  166. kirby_api-0.1.0/kirby/characters/services/character_info_backfill.py +105 -0
  167. kirby_api-0.1.0/kirby/characters/services/effective.py +41 -0
  168. kirby_api-0.1.0/kirby/characters/services/entity_service.py +430 -0
  169. kirby_api-0.1.0/kirby/characters/services/writeup_persistence.py +88 -0
  170. kirby_api-0.1.0/kirby/characters/variants_service.py +72 -0
  171. kirby_api-0.1.0/kirby/characters/xp_service.py +113 -0
  172. kirby_api-0.1.0/kirby/cli.py +125 -0
  173. kirby_api-0.1.0/kirby/codex/__init__.py +8 -0
  174. kirby_api-0.1.0/kirby/codex/cli.py +209 -0
  175. kirby_api-0.1.0/kirby/codex/deps.py +54 -0
  176. kirby_api-0.1.0/kirby/codex/models.py +215 -0
  177. kirby_api-0.1.0/kirby/codex/repositories.py +233 -0
  178. kirby_api-0.1.0/kirby/codex/routes.py +291 -0
  179. kirby_api-0.1.0/kirby/codex/schemas.py +97 -0
  180. kirby_api-0.1.0/kirby/codex/services/__init__.py +1 -0
  181. kirby_api-0.1.0/kirby/codex/services/embed.py +46 -0
  182. kirby_api-0.1.0/kirby/codex/services/extract.py +322 -0
  183. kirby_api-0.1.0/kirby/codex/services/index.py +68 -0
  184. kirby_api-0.1.0/kirby/codex/services/orchestrator.py +203 -0
  185. kirby_api-0.1.0/kirby/codex/services/search.py +358 -0
  186. kirby_api-0.1.0/kirby/codex/services/villain_extractor.py +139 -0
  187. kirby_api-0.1.0/kirby/codex/services/villain_writeup_linker.py +157 -0
  188. kirby_api-0.1.0/kirby/codex/workers.py +96 -0
  189. kirby_api-0.1.0/kirby/combat/__init__.py +47 -0
  190. kirby_api-0.1.0/kirby/combat/choosers.py +249 -0
  191. kirby_api-0.1.0/kirby/combat/create.py +327 -0
  192. kirby_api-0.1.0/kirby/combat/events.py +207 -0
  193. kirby_api-0.1.0/kirby/combat/harness.py +498 -0
  194. kirby_api-0.1.0/kirby/combat/models.py +298 -0
  195. kirby_api-0.1.0/kirby/combat/random_fight/__init__.py +0 -0
  196. kirby_api-0.1.0/kirby/combat/random_fight/models.py +77 -0
  197. kirby_api-0.1.0/kirby/combat/random_fight/outcome.py +40 -0
  198. kirby_api-0.1.0/kirby/combat/random_fight/roster.py +223 -0
  199. kirby_api-0.1.0/kirby/combat/random_fight/routes.py +176 -0
  200. kirby_api-0.1.0/kirby/combat/random_fight/runner.py +259 -0
  201. kirby_api-0.1.0/kirby/combat/random_fight/schemas.py +99 -0
  202. kirby_api-0.1.0/kirby/combat/random_fight/session.py +313 -0
  203. kirby_api-0.1.0/kirby/combat/random_fight/worker.py +107 -0
  204. kirby_api-0.1.0/kirby/combat/routes.py +1122 -0
  205. kirby_api-0.1.0/kirby/combat/schemas.py +309 -0
  206. kirby_api-0.1.0/kirby/combat/services/__init__.py +1 -0
  207. kirby_api-0.1.0/kirby/combat/services/connection_hub.py +73 -0
  208. kirby_api-0.1.0/kirby/combat/services/publish.py +129 -0
  209. kirby_api-0.1.0/kirby/combat/services/subscriber.py +121 -0
  210. kirby_api-0.1.0/kirby/comic_canon/__init__.py +30 -0
  211. kirby_api-0.1.0/kirby/comic_canon/models.py +342 -0
  212. kirby_api-0.1.0/kirby/comic_canon/routes.py +385 -0
  213. kirby_api-0.1.0/kirby/comic_canon/services/__init__.py +0 -0
  214. kirby_api-0.1.0/kirby/comic_canon/services/archetypes.py +183 -0
  215. kirby_api-0.1.0/kirby/comic_canon/services/comic_vine.py +215 -0
  216. kirby_api-0.1.0/kirby/comic_canon/services/embeddings.py +85 -0
  217. kirby_api-0.1.0/kirby/comic_canon/services/prewarm.py +455 -0
  218. kirby_api-0.1.0/kirby/comic_canon/services/text_clean.py +28 -0
  219. kirby_api-0.1.0/kirby/engines/__init__.py +1 -0
  220. kirby_api-0.1.0/kirby/engines/cost/__init__.py +46 -0
  221. kirby_api-0.1.0/kirby/engines/cost/hdc.py +200 -0
  222. kirby_api-0.1.0/kirby/library/__init__.py +1 -0
  223. kirby_api-0.1.0/kirby/main.py +283 -0
  224. kirby_api-0.1.0/kirby/media/__init__.py +11 -0
  225. kirby_api-0.1.0/kirby/media/asset_uses.py +36 -0
  226. kirby_api-0.1.0/kirby/media/client.py +29 -0
  227. kirby_api-0.1.0/kirby/media/routes.py +168 -0
  228. kirby_api-0.1.0/kirby/media/schemas.py +31 -0
  229. kirby_api-0.1.0/kirby/media/taxonomy.py +33 -0
  230. kirby_api-0.1.0/kirby/settings.py +113 -0
  231. kirby_api-0.1.0/kirby/shared/__init__.py +1 -0
  232. kirby_api-0.1.0/kirby/shared/db.py +69 -0
  233. kirby_api-0.1.0/kirby/teams/__init__.py +0 -0
  234. kirby_api-0.1.0/kirby/teams/models.py +209 -0
  235. kirby_api-0.1.0/kirby/teams/routes.py +234 -0
  236. kirby_api-0.1.0/kirby/teams/schemas.py +123 -0
  237. kirby_api-0.1.0/kirby/teams/services/__init__.py +0 -0
  238. kirby_api-0.1.0/kirby/teams/services/effective.py +63 -0
  239. kirby_api-0.1.0/kirby/templates/__init__.py +1 -0
  240. kirby_api-0.1.0/kirby/tenants/__init__.py +1 -0
  241. kirby_api-0.1.0/kirby/tenants/models.py +89 -0
  242. kirby_api-0.1.0/kirby/tenants/routes.py +145 -0
  243. kirby_api-0.1.0/kirby/users/__init__.py +0 -0
  244. kirby_api-0.1.0/kirby/users/routes.py +40 -0
  245. kirby_api-0.1.0/kirby/version.py +97 -0
  246. kirby_api-0.1.0/kirby_api.egg-info/PKG-INFO +273 -0
  247. kirby_api-0.1.0/kirby_api.egg-info/SOURCES.txt +253 -0
  248. kirby_api-0.1.0/kirby_api.egg-info/dependency_links.txt +1 -0
  249. kirby_api-0.1.0/kirby_api.egg-info/entry_points.txt +4 -0
  250. kirby_api-0.1.0/kirby_api.egg-info/requires.txt +40 -0
  251. kirby_api-0.1.0/kirby_api.egg-info/top_level.txt +1 -0
  252. kirby_api-0.1.0/pyproject.toml +296 -0
  253. kirby_api-0.1.0/setup.cfg +4 -0
  254. kirby_api-0.1.0/tests/test_teardown.py +99 -0
  255. kirby_api-0.1.0/tests/test_wheel_contents.py +168 -0
@@ -0,0 +1,170 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all
9
+ your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the
14
+ software to do everything you might do with the software
15
+ that would otherwise infringe the licensor's copyright
16
+ in it for any permitted purpose. However, you may
17
+ only distribute the software according to [Distribution
18
+ License](#distribution-license) and make changes or new works
19
+ based on the software according to [Changes and New Works
20
+ License](#changes-and-new-works-license).
21
+
22
+ ## Distribution License
23
+
24
+ The licensor grants you an additional copyright license
25
+ to distribute copies of the software. Your license
26
+ to distribute covers distributing the software with
27
+ changes and new works permitted by [Changes and New Works
28
+ License](#changes-and-new-works-license).
29
+
30
+ ## Notices
31
+
32
+ You must ensure that anyone who gets a copy of any part of
33
+ the software from you also gets a copy of these terms or the
34
+ URL for them above, as well as copies of any plain-text lines
35
+ beginning with `Required Notice:` that the licensor provided
36
+ with the software. For example:
37
+
38
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
39
+
40
+ ## Changes and New Works License
41
+
42
+ The licensor grants you an additional copyright license to
43
+ make changes and new works based on the software for any
44
+ permitted purpose.
45
+
46
+ ## Patent License
47
+
48
+ The licensor grants you a patent license for the software that
49
+ covers patent claims the licensor can license, or becomes able
50
+ to license, that you would infringe by using the software.
51
+
52
+ ## Noncommercial Purposes
53
+
54
+ Any noncommercial purpose is a permitted purpose.
55
+
56
+ ## Personal Uses
57
+
58
+ Personal use for research, experiment, and testing for
59
+ the benefit of public knowledge, personal study, private
60
+ entertainment, hobby projects, amateur pursuits, or religious
61
+ observance, without any anticipated commercial application,
62
+ is use for a permitted purpose.
63
+
64
+ ## Noncommercial Organizations
65
+
66
+ Use by any charitable organization, educational institution,
67
+ public research organization, public safety or health
68
+ organization, environmental protection organization,
69
+ or government institution is use for a permitted purpose
70
+ regardless of the source of funding or obligations resulting
71
+ from the funding.
72
+
73
+ ## Fair Use
74
+
75
+ You may have "fair use" rights for the software under the
76
+ law. These terms do not limit them.
77
+
78
+ ## No Other Rights
79
+
80
+ These terms do not allow you to sublicense or transfer any of
81
+ your licenses to anyone else, or prevent the licensor from
82
+ granting licenses to anyone else. These terms do not imply
83
+ any other licenses.
84
+
85
+ ## Patent Defense
86
+
87
+ If you make any written claim that the software infringes or
88
+ contributes to infringement of any patent, your patent license
89
+ for the software granted under these terms ends immediately. If
90
+ your company makes such a claim, your patent license ends
91
+ immediately for work on behalf of your company.
92
+
93
+ ## Violations
94
+
95
+ The first time you are notified in writing that you have
96
+ violated any of these terms, or done anything with the software
97
+ not covered by your licenses, your licenses can nonetheless
98
+ continue if you come into full compliance with these terms,
99
+ and take practical steps to correct past violations, within
100
+ 32 days of receiving notice. Otherwise, all your licenses
101
+ end immediately.
102
+
103
+ ## No Liability
104
+
105
+ ***As far as the law allows, the software comes as is, without
106
+ any warranty or condition, and the licensor will not be liable
107
+ to you for any damages arising out of these terms or the use
108
+ or nature of the software, under any kind of legal claim.***
109
+
110
+ ## Definitions
111
+
112
+ The **licensor** is the individual or entity offering these
113
+ terms, and the **software** is the software the licensor makes
114
+ available under these terms.
115
+
116
+ **You** refers to the individual or entity agreeing to these
117
+ terms.
118
+
119
+ **Your company** is any legal entity, sole proprietorship,
120
+ or other kind of organization that you work for, plus all
121
+ organizations that have control over, are under the control of,
122
+ or are under common control with that organization. **Control**
123
+ means ownership of substantially all the assets of an entity,
124
+ or the power to direct its management and policies by vote,
125
+ contract, or otherwise. Control can be direct or indirect.
126
+
127
+ **Your licenses** are all the licenses granted to you for the
128
+ software under these terms.
129
+
130
+ **Use** means anything you do with the software requiring one
131
+ of your licenses.
132
+
133
+
134
+ ================================================================================
135
+ NOTES SPECIFIC TO kirby-cost
136
+ ================================================================================
137
+
138
+ Required Notice: Copyright (c) 2026 Peter D Bethke
139
+
140
+ (The line above begins with `Required Notice:` deliberately -- see the
141
+ Notices section of the licence. It must travel with any copy of this
142
+ software or any part of it.)
143
+
144
+ Why this licence
145
+ ----------------
146
+
147
+ kirby-cost is a Python port of the HERO Designer cost engine. The intent of
148
+ choosing a noncommercial licence is explicit: nobody should be able to build a
149
+ paid product on this work, because doing so would compete with HERO Designer
150
+ and with Hero Games' interests.
151
+
152
+ The Elastic License 2.0 was considered and rejected for this purpose. ELv2's
153
+ only commercial limitation is against offering the software as a hosted or
154
+ managed service; it permits selling a non-hosted product outright. That does
155
+ not serve the intent above.
156
+
157
+ Third-party rights this licence does NOT grant
158
+ -----------------------------------------------
159
+
160
+ This work derives from HERO Designer source licensed from CompNet Design, Inc.
161
+ That licence grants personal use and modification; distribution is separately
162
+ gated on licensing with HERO Games. Nothing in this file grants, or purports to
163
+ grant, any right in HERO Designer's source, in the HERO System rules, or in any
164
+ Hero Games property.
165
+
166
+ kirby-cost ships NO Hero Games data. It requires the operator to supply their
167
+ own licensed HERO Designer template (for example Main6E.hdt). Running this
168
+ software without such a licence is the operator's responsibility.
169
+
170
+ HERO System and HERO Designer are the property of their respective owners.
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: kirby-api
3
+ Version: 0.1.0
4
+ Summary: Kirby — HERO System VTT backend API (FastAPI mono)
5
+ Author: PeterB
6
+ License: PolyForm Noncommercial License 1.0.0
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: fastapi<0.136,>=0.115
11
+ Requires-Dist: starlette<1.0,>=0.46
12
+ Requires-Dist: uvicorn[standard]>=0.32
13
+ Requires-Dist: httpx>=0.28
14
+ Requires-Dist: sqlalchemy>=2.0
15
+ Requires-Dist: alembic>=1.14
16
+ Requires-Dist: psycopg[binary]>=3.2
17
+ Requires-Dist: asyncpg>=0.30
18
+ Requires-Dist: pydantic>=2.10
19
+ Requires-Dist: pydantic-settings>=2.7
20
+ Requires-Dist: authlib>=1.3
21
+ Requires-Dist: cryptography>=44
22
+ Requires-Dist: python-jose[cryptography]>=3.3
23
+ Requires-Dist: pyjwt[crypto]>=2.10
24
+ Requires-Dist: redis>=5.2
25
+ Requires-Dist: kirby-cost>=0.6.1
26
+ Requires-Dist: lxml>=5.3
27
+ Requires-Dist: python-multipart>=0.0.20
28
+ Requires-Dist: email-validator>=2.2
29
+ Requires-Dist: docling>=2.91
30
+ Requires-Dist: sentence-transformers>=3.0
31
+ Requires-Dist: pgvector>=0.3
32
+ Requires-Dist: pypdfium2>=4.30
33
+ Requires-Dist: kirby-combat==0.18.5
34
+ Requires-Dist: kirby-dice>=0.1.0
35
+ Requires-Dist: kirby-ai>=0.3.0
36
+ Requires-Dist: prometheus-client>=0.21
37
+ Requires-Dist: prometheus-fastapi-instrumentator>=7.0
38
+ Requires-Dist: tenacity>=8.0
39
+ Provides-Extra: dev
40
+ Requires-Dist: build>=1.2; extra == "dev"
41
+ Requires-Dist: pytest>=8.3; extra == "dev"
42
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
43
+ Requires-Dist: pytest-cov>=6.0; extra == "dev"
44
+ Requires-Dist: httpx>=0.28; extra == "dev"
45
+ Requires-Dist: httpx-ws>=0.7; extra == "dev"
46
+ Requires-Dist: ruff>=0.8; extra == "dev"
47
+ Requires-Dist: mypy>=1.13; extra == "dev"
48
+ Requires-Dist: testcontainers[postgres]>=4.0; extra == "dev"
49
+ Dynamic: license-file
50
+
51
+ # kirby-api
52
+
53
+ FastAPI mono backend for **Kirby**, a HERO System VTT platform. Runs on Hetzner as a systemd service behind Cloudflare Tunnel at `kirby-api.productbinder.net`, port 9013 internally. Serves the SvelteKit frontend at `kirby.productbinder.io` (see `../kirby-app/`) plus the forked Foundry module that talks to it.
54
+
55
+ This is the backend half of a two-service stack. The frontend lives at `../kirby-app/`. The full design is in `../kirby/docs/superpowers/specs/2026-04-07-kirby-design.md`.
56
+
57
+ ## What's here (minimum viable skeleton)
58
+
59
+ - `kirby/` — the Python package (FastAPI app, domains, engines)
60
+ - `main.py` — FastAPI application entry point
61
+ - `settings.py` — pydantic-settings config loaded from env
62
+ - `auth/` — Mother Box: Discord OAuth, JWT issuance, JWKS, middleware
63
+ - `tenants/` — multi-tenant-by-GM scoping
64
+ - `characters/` — character CRUD, feature-flag-gated editor
65
+ - `services/entity_service.py` — HDC import with byte-preserving round-trip; substitutes no value, refuses instead
66
+ - `campaigns/` — campaign management
67
+ - `library/` — The Source: bestiary, scenarios, lore
68
+ - `media/` — Boom Tube: R2-backed asset storage
69
+ - `templates/` — HDT template management (Main6E + 16 others)
70
+ - `engines/` — pluggable engine modules
71
+ - `cost/` — cost engine (absorbed from kirby-cost)
72
+ - `combat/` — **Krackle**: a HARNESS over the `kirby-combat` engine (see below)
73
+ - `models.py` — four SQLAlchemy tables (combat_session, combat_participant, combat_event, combat_narration)
74
+ - `schemas.py` — Pydantic request/response shapes
75
+ - `harness.py` — rehydrate-by-replay, one Phase per step, pinned builds, derived dice
76
+ - `create.py` — seating a fight: rows, pinned builds, the engine's opening. Commits nothing
77
+ - `choosers.py` — routes each fighter's Phase to its seat (player submission or model)
78
+ - `events.py` — engine event ↔ `combat_event` row
79
+ - `routes.py` — REST + WebSocket under `/api/combat/*`
80
+ - `random_fight/` — matchmaking an N-way free-for-all, and the worker that runs it
81
+ - `services/publish.py` + `services/subscriber.py` — Redis pub/sub fan-out
82
+ - `admin/` — operator-only routes
83
+ - `shared/` — DB session, Redis client, tenant-query helpers, errors
84
+ - `alembic/` — migration files
85
+ - `data/` — engine data files (template_6e.json, language_chart.json, etc.)
86
+ - `tests/` — pytest suite (unit, integration, fixtures, combat/)
87
+
88
+ ## Combat module (Krackle) — a harness, not an engine
89
+
90
+ kirby-api resolves no rule. `kirby-combat` decides who acts, what the menu
91
+ is, whether the Segment is spent and whether the fight is over;
92
+ `kirby-ai` chooses and narrates. What this module owns is HTTP,
93
+ persistence, tenancy, fan-out and matchmaking. There is no api-side menu,
94
+ no api-side mapping of an engine word onto a local one, and no fallback:
95
+ `run_phase(..., on_unresolvable="raise")` always, so a kind the engine
96
+ cannot execute stops the fight instead of quietly becoming a rule not
97
+ applied.
98
+
99
+ The design is
100
+ [`../kirby/docs/superpowers/specs/2026-09-17-api-is-a-harness-design.md`](../kirby/docs/superpowers/specs/2026-09-17-api-is-a-harness-design.md).
101
+
102
+ ### The surface
103
+
104
+ | Route | What it does |
105
+ | --- | --- |
106
+ | `POST /api/combat/sessions` | Seat a fight: participants, `template_id`, `seat`, optional `narration_seat`, `dice_seed`, `scene_seed`. GM only. |
107
+ | `GET /api/combat/sessions/{id}` | The fight's identity plus the state its log folds to and its `scene` (the engine's `Scene` — walls, surfaces, hazards, constructs — `None` when `scene_seed` is `None`). |
108
+ | `GET .../situation` | The Phase the engine would offer next, without running it. |
109
+ | `POST .../step` | Run ONE Phase with the fight's own seats. GM only. |
110
+ | `POST .../choose` | Declare for the combatant whose Phase it is, then run that Phase. |
111
+ | `POST .../run` | Repeated `step` to a decision or a Turn cap. Its `events`/`narration` are ONE PAGE (the same 500 cap `GET .../events` has) with `truncated` and `last_sequence` to page from. |
112
+ | `GET .../events` | The record, paged (`since=`, `limit`). |
113
+ | `GET .../state?at=` | The fight as it stood at a sequence — the ENGINE's `state_view()`, forwarded unchanged: vitals, position and facing, conditions, who each man can perceive, and whose Phase is next. A sequence past the record is a 400, never a silent clamp. |
114
+ | `GET .../narration` | The narration table: at most one line per Phase, keyed by the engine's sequence, each carrying `speaker_id` — the Phase actor's combatant id, in the same vocabulary as `combat_participant`'s combatant id — beside the `seat` that spoke it. |
115
+ | `WS .../live` | Live events; multi-worker fan-out over the `combat:{id}` Redis channel. |
116
+ | `GET /api/combat/version` | The engine and seat-package versions this deployment runs. A client that generated its event types from one engine's schema compares and refuses; this route holds no opinion about the comparison. |
117
+ | `POST /api/combat/random-fight` | Matchmake an N-way free-for-all and queue the loop. GM only. |
118
+
119
+ `POST /api/combat/random-fight` **needs `KIRBY_CORPUS_TENANT_ID` set to
120
+ the tenant the villain books were imported into.** The matchmaker draws
121
+ from that tenant and the caller's own, and from nowhere else: the
122
+ production corpus lives in the kirby-api database, `character_def.source`
123
+ is set freely by the caller on create, so `source IN ('CV1','CV2','CV3',
124
+ 'OSE')` was never a fence. Unset, the route refuses with 409 rather than
125
+ opening the pool to every tenant.
126
+
127
+ `step`, `choose` and `run` report two failures rather than hiding them:
128
+ `narration_error` (the narration seat raised; the Phase stands) and
129
+ `publish_error` (the fan-out raised; the Phase stands and is on disk).
130
+ Neither is ever a placeholder — "the seat failed" and "he said nothing"
131
+ stay different facts.
132
+
133
+ ### What playback promises
134
+
135
+ **The record holds the build the fight was fought with.** Each
136
+ participant's costed build doc is pinned into
137
+ `combat_participant.build_jsonb` when the fight is created, so every
138
+ rehydration rebuilds the starting men from the sheet they started with —
139
+ an XP spend, a variant or a re-import afterwards cannot rewrite that
140
+ fight's history.
141
+
142
+ **The api projects nothing here.** `state` on `state?at=`, on
143
+ `GET /sessions/{id}` and on `step` / `choose` / `run` is the engine's own
144
+ `kirby_combat.state_view()`, serialised by the engine's own `to_dict`, so
145
+ what a client reads is what the engine folded and not a second reading of
146
+ the log made here.
147
+
148
+ Rehydration is replay and only replay: the starting combatants from those
149
+ pinned builds, the arena from `scene_seed` (the engine generates it), then
150
+ the engine's own `apply_event` over every persisted row in order. There is
151
+ no snapshot and no mutable per-fight state column to drift from the
152
+ record.
153
+
154
+ Two honest limits:
155
+
156
+ - **Rolls are not logged.** Each step's roller is derived as
157
+ `blake2b(f"{dice_seed}:{next_sequence}")`, so the dice a step drew are
158
+ reproducible from the record — but no roll is stored, and a fight whose
159
+ `dice_seed` is NULL (the row saying RANDOM) cannot be re-derived at all.
160
+ State replays; the rolls behind it are re-derived, not read back.
161
+ - **A library release can re-cost a finished fight's numbers.** The build
162
+ doc in the record is fixed, but a cost read off it comes from whichever
163
+ `kirby-cost` is installed. The fight's events do not change; a total
164
+ displayed beside them can.
165
+
166
+ ## Install and run
167
+
168
+ `kirby-api` is published to public PyPI. It runs wherever Python 3.11+ and
169
+ Postgres run; the Hetzner box is one consumer of the published version, not
170
+ the place the software lives.
171
+
172
+ ```bash
173
+ pip install kirby-api
174
+ kirby-api migrate # alembic upgrade head, against the packaged migrations
175
+ kirby-api serve # uvicorn on kirby.main:app
176
+ ```
177
+
178
+ `serve` takes `--host`, `--port`, `--workers` and `--log-level`. The
179
+ migrations ship inside the wheel, so `migrate` needs no checkout.
180
+
181
+ ### What it needs in the environment
182
+
183
+ Nothing has a production default, and a missing value is refused rather than
184
+ guessed at:
185
+
186
+ | Variable | What it is |
187
+ | --- | --- |
188
+ | `DATABASE_URL` | Postgres, with the `vector` extension available (pgvector). **Required** — both commands refuse and name it if it is unset. |
189
+ | `REDIS_URL` | Redis, for cross-worker WebSocket fan-out of live combat. |
190
+ | `KIRBY_MEDIALIB_API_URL` | The medialib instance every generation call goes through. kirby-api holds no model table and no provider key. |
191
+ | `TANNHAUSER_ISSUER` / `TANNHAUSER_JWKS_URL` / `TANNHAUSER_AUDIENCE` | The identity provider. kirby-api is a relying party: it validates JWTs, it never issues them. |
192
+ | `KIRBY_CORPUS_TENANT_ID` | The tenant holding the canonical character corpus the random fight matchmakes from. Unset means "the caller's own tenant only" — a deployment that has not said where its corpus lives gets a refusal, not somebody else's characters. |
193
+ | `KIRBY_API_URL` / `KIRBY_APP_URL` / `CORS_ORIGINS` | Self URL, frontend URL, and the origins allowed to call the API. |
194
+
195
+ `.env.example` carries the full list. No secret value belongs in any of the
196
+ files in this repository.
197
+
198
+ ### A note on the test suite
199
+
200
+ Part of the suite reads Hero Games licensed material — the character corpus
201
+ and the HERO Designer `.hdt` template, addressed by `KIRBY_CORPUS_URL`,
202
+ `OSE_HDC_DIR`, `CHAMPIONS_HDC_DIR` and `KIRBY_COST_HDT`. That material is
203
+ never vendored, and those tests skip themselves wherever it is absent. They
204
+ gate the founder's own deployment; they do not gate a release of the
205
+ software, and the release workflow runs without any of it.
206
+
207
+ ## Running locally
208
+
209
+ ```bash
210
+ cd kirby-api
211
+ python -m venv .venv
212
+ source .venv/bin/activate
213
+ pip install -e ".[dev]"
214
+ pip install -e ../kirby-cost # cost engine, see below
215
+ cp .env.example .env
216
+ # edit .env with your local Postgres credentials + Discord app credentials
217
+ alembic upgrade head
218
+ uvicorn kirby.main:app --reload --port 9013
219
+ ```
220
+
221
+ Then `curl http://127.0.0.1:9013/health` should return `{"status": "ok"}`.
222
+
223
+ ### Cost engine setup
224
+
225
+ The cost engine (HDC parsing + HERO 6E cost math) lives in the sibling
226
+ `kirby-cost` repo and is consumed via the
227
+ `kirby.engines.cost` facade (see `kirby/engines/cost/__init__.py`).
228
+ Install it as an editable local package:
229
+
230
+ ```bash
231
+ .venv/bin/pip install -e ../kirby-cost
232
+ ```
233
+
234
+ This is a sibling-repo editable install — same pattern Codex uses for
235
+ PyTorch — so it's not declared in `pyproject.toml`. The facade re-exports
236
+ `HDCLoader`, `LoadedHero`, `Modifier`, `Adder`, `Multipower`,
237
+ `VariablePowerPool`, and `ElementalControl`, plus `load_hdc(bytes)` and
238
+ `load_hdc_from_path(path)` for the REST upload path.
239
+
240
+ ## Running on Hetzner (production)
241
+
242
+ Via `kirby-api.service` (systemd), `git pull` + restart. See the design spec for full deploy notes. Mirrors the `medialib-api` operational shape.
243
+
244
+ ## Environment variables
245
+
246
+ See `.env.example`. Key ones:
247
+
248
+ - `DATABASE_URL` — Postgres connection string (port 15432 on Hetzner, 5432 locally usually)
249
+ - `REDIS_URL` — Redis connection string (port 16379 on Hetzner)
250
+ - `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` — Discord OAuth app credentials
251
+ - `JWT_PRIVATE_KEY_PATH` / `JWT_PUBLIC_KEY_PATH` — RSA keypair for RS256 JWT signing
252
+ - `KIRBY_API_URL` — self-URL for OIDC discovery (`https://kirby-api.productbinder.net` in prod)
253
+ - `KIRBY_APP_URL` — frontend URL for OAuth callback redirects (`https://kirby.productbinder.io` in prod)
254
+
255
+ ## Engine provenance (short version)
256
+
257
+ - **Cost engine** — Python port of HERO Designer's math, built and validated against the Java HD oracle (655 fixtures, 28,593 / 28,593 cost calculations matching). Fully covered by PeterB's $50 HD source code license purchased from herogames.com on 2026-04-07 (Simon's license, customer #29 ever).
258
+ - **Combat engine** — Pure-Python port of `dmdorman/hero6e-foundryvtt` (MIT). Lives in the separate `kirby-combat` package (`git@github.com:pdbethke/kirby-combat.git`), consumed as a library. Attribution to Dorman in `kirby-combat/LICENSE` and README. Phase 1 (to-hit, damage, defense, knockback, status, action pipeline) shipped 2026-04-16 with 98% test coverage. **This is the live combat engine — it is NOT conditional on any NDA.** An earlier plan to port from David Tannen's HCM under NDA was abandoned when David became unresponsive; PeterB's written commitments to David (no HCM source, no decompiled HCM reliance, Foundry-talking-backend architecture) are all honored by the Dorman-based approach.
259
+
260
+ ## What publishes, and what never does
261
+
262
+ The **software** publishes: `kirby-api` goes to public PyPI alongside
263
+ `kirby-combat`, `kirby-cost`, `kirby-dice` and `kirby-ai` (founder decision,
264
+ 2026-09-18). It is a product, not a box.
265
+
266
+ The **licensed material does not, ever**: no Hero Games character corpus, no
267
+ `.hdc` files, no HERO Designer `.hdt` templates, no HD source. None of it is
268
+ in this repository, none of it is in the wheel, and
269
+ `tests/test_wheel_contents.py` fails the build if anything secret-shaped
270
+ turns up in package data. See `project_hd_source_license.md`,
271
+ `project_david_tannen_emails.md` and `project_hero_games_org_chart.md` in
272
+ Claude's project memory for the full licensing posture and the commitments
273
+ PeterB has made.
@@ -0,0 +1,223 @@
1
+ # kirby-api
2
+
3
+ FastAPI mono backend for **Kirby**, a HERO System VTT platform. Runs on Hetzner as a systemd service behind Cloudflare Tunnel at `kirby-api.productbinder.net`, port 9013 internally. Serves the SvelteKit frontend at `kirby.productbinder.io` (see `../kirby-app/`) plus the forked Foundry module that talks to it.
4
+
5
+ This is the backend half of a two-service stack. The frontend lives at `../kirby-app/`. The full design is in `../kirby/docs/superpowers/specs/2026-04-07-kirby-design.md`.
6
+
7
+ ## What's here (minimum viable skeleton)
8
+
9
+ - `kirby/` — the Python package (FastAPI app, domains, engines)
10
+ - `main.py` — FastAPI application entry point
11
+ - `settings.py` — pydantic-settings config loaded from env
12
+ - `auth/` — Mother Box: Discord OAuth, JWT issuance, JWKS, middleware
13
+ - `tenants/` — multi-tenant-by-GM scoping
14
+ - `characters/` — character CRUD, feature-flag-gated editor
15
+ - `services/entity_service.py` — HDC import with byte-preserving round-trip; substitutes no value, refuses instead
16
+ - `campaigns/` — campaign management
17
+ - `library/` — The Source: bestiary, scenarios, lore
18
+ - `media/` — Boom Tube: R2-backed asset storage
19
+ - `templates/` — HDT template management (Main6E + 16 others)
20
+ - `engines/` — pluggable engine modules
21
+ - `cost/` — cost engine (absorbed from kirby-cost)
22
+ - `combat/` — **Krackle**: a HARNESS over the `kirby-combat` engine (see below)
23
+ - `models.py` — four SQLAlchemy tables (combat_session, combat_participant, combat_event, combat_narration)
24
+ - `schemas.py` — Pydantic request/response shapes
25
+ - `harness.py` — rehydrate-by-replay, one Phase per step, pinned builds, derived dice
26
+ - `create.py` — seating a fight: rows, pinned builds, the engine's opening. Commits nothing
27
+ - `choosers.py` — routes each fighter's Phase to its seat (player submission or model)
28
+ - `events.py` — engine event ↔ `combat_event` row
29
+ - `routes.py` — REST + WebSocket under `/api/combat/*`
30
+ - `random_fight/` — matchmaking an N-way free-for-all, and the worker that runs it
31
+ - `services/publish.py` + `services/subscriber.py` — Redis pub/sub fan-out
32
+ - `admin/` — operator-only routes
33
+ - `shared/` — DB session, Redis client, tenant-query helpers, errors
34
+ - `alembic/` — migration files
35
+ - `data/` — engine data files (template_6e.json, language_chart.json, etc.)
36
+ - `tests/` — pytest suite (unit, integration, fixtures, combat/)
37
+
38
+ ## Combat module (Krackle) — a harness, not an engine
39
+
40
+ kirby-api resolves no rule. `kirby-combat` decides who acts, what the menu
41
+ is, whether the Segment is spent and whether the fight is over;
42
+ `kirby-ai` chooses and narrates. What this module owns is HTTP,
43
+ persistence, tenancy, fan-out and matchmaking. There is no api-side menu,
44
+ no api-side mapping of an engine word onto a local one, and no fallback:
45
+ `run_phase(..., on_unresolvable="raise")` always, so a kind the engine
46
+ cannot execute stops the fight instead of quietly becoming a rule not
47
+ applied.
48
+
49
+ The design is
50
+ [`../kirby/docs/superpowers/specs/2026-09-17-api-is-a-harness-design.md`](../kirby/docs/superpowers/specs/2026-09-17-api-is-a-harness-design.md).
51
+
52
+ ### The surface
53
+
54
+ | Route | What it does |
55
+ | --- | --- |
56
+ | `POST /api/combat/sessions` | Seat a fight: participants, `template_id`, `seat`, optional `narration_seat`, `dice_seed`, `scene_seed`. GM only. |
57
+ | `GET /api/combat/sessions/{id}` | The fight's identity plus the state its log folds to and its `scene` (the engine's `Scene` — walls, surfaces, hazards, constructs — `None` when `scene_seed` is `None`). |
58
+ | `GET .../situation` | The Phase the engine would offer next, without running it. |
59
+ | `POST .../step` | Run ONE Phase with the fight's own seats. GM only. |
60
+ | `POST .../choose` | Declare for the combatant whose Phase it is, then run that Phase. |
61
+ | `POST .../run` | Repeated `step` to a decision or a Turn cap. Its `events`/`narration` are ONE PAGE (the same 500 cap `GET .../events` has) with `truncated` and `last_sequence` to page from. |
62
+ | `GET .../events` | The record, paged (`since=`, `limit`). |
63
+ | `GET .../state?at=` | The fight as it stood at a sequence — the ENGINE's `state_view()`, forwarded unchanged: vitals, position and facing, conditions, who each man can perceive, and whose Phase is next. A sequence past the record is a 400, never a silent clamp. |
64
+ | `GET .../narration` | The narration table: at most one line per Phase, keyed by the engine's sequence, each carrying `speaker_id` — the Phase actor's combatant id, in the same vocabulary as `combat_participant`'s combatant id — beside the `seat` that spoke it. |
65
+ | `WS .../live` | Live events; multi-worker fan-out over the `combat:{id}` Redis channel. |
66
+ | `GET /api/combat/version` | The engine and seat-package versions this deployment runs. A client that generated its event types from one engine's schema compares and refuses; this route holds no opinion about the comparison. |
67
+ | `POST /api/combat/random-fight` | Matchmake an N-way free-for-all and queue the loop. GM only. |
68
+
69
+ `POST /api/combat/random-fight` **needs `KIRBY_CORPUS_TENANT_ID` set to
70
+ the tenant the villain books were imported into.** The matchmaker draws
71
+ from that tenant and the caller's own, and from nowhere else: the
72
+ production corpus lives in the kirby-api database, `character_def.source`
73
+ is set freely by the caller on create, so `source IN ('CV1','CV2','CV3',
74
+ 'OSE')` was never a fence. Unset, the route refuses with 409 rather than
75
+ opening the pool to every tenant.
76
+
77
+ `step`, `choose` and `run` report two failures rather than hiding them:
78
+ `narration_error` (the narration seat raised; the Phase stands) and
79
+ `publish_error` (the fan-out raised; the Phase stands and is on disk).
80
+ Neither is ever a placeholder — "the seat failed" and "he said nothing"
81
+ stay different facts.
82
+
83
+ ### What playback promises
84
+
85
+ **The record holds the build the fight was fought with.** Each
86
+ participant's costed build doc is pinned into
87
+ `combat_participant.build_jsonb` when the fight is created, so every
88
+ rehydration rebuilds the starting men from the sheet they started with —
89
+ an XP spend, a variant or a re-import afterwards cannot rewrite that
90
+ fight's history.
91
+
92
+ **The api projects nothing here.** `state` on `state?at=`, on
93
+ `GET /sessions/{id}` and on `step` / `choose` / `run` is the engine's own
94
+ `kirby_combat.state_view()`, serialised by the engine's own `to_dict`, so
95
+ what a client reads is what the engine folded and not a second reading of
96
+ the log made here.
97
+
98
+ Rehydration is replay and only replay: the starting combatants from those
99
+ pinned builds, the arena from `scene_seed` (the engine generates it), then
100
+ the engine's own `apply_event` over every persisted row in order. There is
101
+ no snapshot and no mutable per-fight state column to drift from the
102
+ record.
103
+
104
+ Two honest limits:
105
+
106
+ - **Rolls are not logged.** Each step's roller is derived as
107
+ `blake2b(f"{dice_seed}:{next_sequence}")`, so the dice a step drew are
108
+ reproducible from the record — but no roll is stored, and a fight whose
109
+ `dice_seed` is NULL (the row saying RANDOM) cannot be re-derived at all.
110
+ State replays; the rolls behind it are re-derived, not read back.
111
+ - **A library release can re-cost a finished fight's numbers.** The build
112
+ doc in the record is fixed, but a cost read off it comes from whichever
113
+ `kirby-cost` is installed. The fight's events do not change; a total
114
+ displayed beside them can.
115
+
116
+ ## Install and run
117
+
118
+ `kirby-api` is published to public PyPI. It runs wherever Python 3.11+ and
119
+ Postgres run; the Hetzner box is one consumer of the published version, not
120
+ the place the software lives.
121
+
122
+ ```bash
123
+ pip install kirby-api
124
+ kirby-api migrate # alembic upgrade head, against the packaged migrations
125
+ kirby-api serve # uvicorn on kirby.main:app
126
+ ```
127
+
128
+ `serve` takes `--host`, `--port`, `--workers` and `--log-level`. The
129
+ migrations ship inside the wheel, so `migrate` needs no checkout.
130
+
131
+ ### What it needs in the environment
132
+
133
+ Nothing has a production default, and a missing value is refused rather than
134
+ guessed at:
135
+
136
+ | Variable | What it is |
137
+ | --- | --- |
138
+ | `DATABASE_URL` | Postgres, with the `vector` extension available (pgvector). **Required** — both commands refuse and name it if it is unset. |
139
+ | `REDIS_URL` | Redis, for cross-worker WebSocket fan-out of live combat. |
140
+ | `KIRBY_MEDIALIB_API_URL` | The medialib instance every generation call goes through. kirby-api holds no model table and no provider key. |
141
+ | `TANNHAUSER_ISSUER` / `TANNHAUSER_JWKS_URL` / `TANNHAUSER_AUDIENCE` | The identity provider. kirby-api is a relying party: it validates JWTs, it never issues them. |
142
+ | `KIRBY_CORPUS_TENANT_ID` | The tenant holding the canonical character corpus the random fight matchmakes from. Unset means "the caller's own tenant only" — a deployment that has not said where its corpus lives gets a refusal, not somebody else's characters. |
143
+ | `KIRBY_API_URL` / `KIRBY_APP_URL` / `CORS_ORIGINS` | Self URL, frontend URL, and the origins allowed to call the API. |
144
+
145
+ `.env.example` carries the full list. No secret value belongs in any of the
146
+ files in this repository.
147
+
148
+ ### A note on the test suite
149
+
150
+ Part of the suite reads Hero Games licensed material — the character corpus
151
+ and the HERO Designer `.hdt` template, addressed by `KIRBY_CORPUS_URL`,
152
+ `OSE_HDC_DIR`, `CHAMPIONS_HDC_DIR` and `KIRBY_COST_HDT`. That material is
153
+ never vendored, and those tests skip themselves wherever it is absent. They
154
+ gate the founder's own deployment; they do not gate a release of the
155
+ software, and the release workflow runs without any of it.
156
+
157
+ ## Running locally
158
+
159
+ ```bash
160
+ cd kirby-api
161
+ python -m venv .venv
162
+ source .venv/bin/activate
163
+ pip install -e ".[dev]"
164
+ pip install -e ../kirby-cost # cost engine, see below
165
+ cp .env.example .env
166
+ # edit .env with your local Postgres credentials + Discord app credentials
167
+ alembic upgrade head
168
+ uvicorn kirby.main:app --reload --port 9013
169
+ ```
170
+
171
+ Then `curl http://127.0.0.1:9013/health` should return `{"status": "ok"}`.
172
+
173
+ ### Cost engine setup
174
+
175
+ The cost engine (HDC parsing + HERO 6E cost math) lives in the sibling
176
+ `kirby-cost` repo and is consumed via the
177
+ `kirby.engines.cost` facade (see `kirby/engines/cost/__init__.py`).
178
+ Install it as an editable local package:
179
+
180
+ ```bash
181
+ .venv/bin/pip install -e ../kirby-cost
182
+ ```
183
+
184
+ This is a sibling-repo editable install — same pattern Codex uses for
185
+ PyTorch — so it's not declared in `pyproject.toml`. The facade re-exports
186
+ `HDCLoader`, `LoadedHero`, `Modifier`, `Adder`, `Multipower`,
187
+ `VariablePowerPool`, and `ElementalControl`, plus `load_hdc(bytes)` and
188
+ `load_hdc_from_path(path)` for the REST upload path.
189
+
190
+ ## Running on Hetzner (production)
191
+
192
+ Via `kirby-api.service` (systemd), `git pull` + restart. See the design spec for full deploy notes. Mirrors the `medialib-api` operational shape.
193
+
194
+ ## Environment variables
195
+
196
+ See `.env.example`. Key ones:
197
+
198
+ - `DATABASE_URL` — Postgres connection string (port 15432 on Hetzner, 5432 locally usually)
199
+ - `REDIS_URL` — Redis connection string (port 16379 on Hetzner)
200
+ - `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` — Discord OAuth app credentials
201
+ - `JWT_PRIVATE_KEY_PATH` / `JWT_PUBLIC_KEY_PATH` — RSA keypair for RS256 JWT signing
202
+ - `KIRBY_API_URL` — self-URL for OIDC discovery (`https://kirby-api.productbinder.net` in prod)
203
+ - `KIRBY_APP_URL` — frontend URL for OAuth callback redirects (`https://kirby.productbinder.io` in prod)
204
+
205
+ ## Engine provenance (short version)
206
+
207
+ - **Cost engine** — Python port of HERO Designer's math, built and validated against the Java HD oracle (655 fixtures, 28,593 / 28,593 cost calculations matching). Fully covered by PeterB's $50 HD source code license purchased from herogames.com on 2026-04-07 (Simon's license, customer #29 ever).
208
+ - **Combat engine** — Pure-Python port of `dmdorman/hero6e-foundryvtt` (MIT). Lives in the separate `kirby-combat` package (`git@github.com:pdbethke/kirby-combat.git`), consumed as a library. Attribution to Dorman in `kirby-combat/LICENSE` and README. Phase 1 (to-hit, damage, defense, knockback, status, action pipeline) shipped 2026-04-16 with 98% test coverage. **This is the live combat engine — it is NOT conditional on any NDA.** An earlier plan to port from David Tannen's HCM under NDA was abandoned when David became unresponsive; PeterB's written commitments to David (no HCM source, no decompiled HCM reliance, Foundry-talking-backend architecture) are all honored by the Dorman-based approach.
209
+
210
+ ## What publishes, and what never does
211
+
212
+ The **software** publishes: `kirby-api` goes to public PyPI alongside
213
+ `kirby-combat`, `kirby-cost`, `kirby-dice` and `kirby-ai` (founder decision,
214
+ 2026-09-18). It is a product, not a box.
215
+
216
+ The **licensed material does not, ever**: no Hero Games character corpus, no
217
+ `.hdc` files, no HERO Designer `.hdt` templates, no HD source. None of it is
218
+ in this repository, none of it is in the wheel, and
219
+ `tests/test_wheel_contents.py` fails the build if anything secret-shaped
220
+ turns up in package data. See `project_hd_source_license.md`,
221
+ `project_david_tannen_emails.md` and `project_hero_games_org_chart.md` in
222
+ Claude's project memory for the full licensing posture and the commitments
223
+ PeterB has made.