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.
- kirby_api-0.1.0/LICENSE +170 -0
- kirby_api-0.1.0/PKG-INFO +273 -0
- kirby_api-0.1.0/README.md +223 -0
- kirby_api-0.1.0/kirby/__init__.py +13 -0
- kirby_api-0.1.0/kirby/_migrations/README +1 -0
- kirby_api-0.1.0/kirby/_migrations/alembic.ini +149 -0
- kirby_api-0.1.0/kirby/_migrations/env.py +74 -0
- kirby_api-0.1.0/kirby/_migrations/script.py.mako +28 -0
- kirby_api-0.1.0/kirby/_migrations/versions/1b7affd8668e_initial_schema_users_tenants_characters_.py +129 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260424_0001_combat_tables.py +197 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260429_0001_add_combatant_jsonb.py +43 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260501_0001_codex_targeted_pages.py +54 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260501_0002_hd_relational_schema.py +652 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260501_0003_definitions_codex_chunk_id.py +74 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260503_0001_per_template_base_values.py +100 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0001_pr1_relational_combat_schema.py +553 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0002_seed_tactics_and_profiles.py +274 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0003_backfill_combat_relational.py +410 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0004_drop_combat_jsonb_columns.py +84 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0005_pr1_index_audit_fills.py +127 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0006_pending_phase_modifiers.py +67 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0007_combat_session_scene_id.py +41 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0008_morale_expires_at_seq.py +37 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0009_is_stunned.py +35 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0010_tactic_catalog_expansion.py +225 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0011_slam_velocity_tactic.py +118 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0012_mind_control_state.py +45 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0013_mental_illusion_state.py +41 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0014_mental_entangle_state.py +37 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0015_was_surprised.py +46 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0016_catalog_expansion_for_new_actions.py +203 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0017_combat_started.py +37 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0018_physical_entangle.py +34 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0019_stat_drains.py +45 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0020_continuous_effects.py +48 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0021_lockout_state.py +39 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260504_0022_dispelled_powers.py +44 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260506_0001_campaign_management_v1.py +426 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260506_0002_extend_characters_for_pc.py +96 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260506_0003_combatant_control_mode.py +55 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260506_0004_brain_embeddings.py +71 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260507_0001_knowledge_tier.py +395 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260507_0002_normalize_jsonb.py +382 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260507_0003_drop_legacy_jsonb.py +180 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260507_0004_canon_embeddings.py +62 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260508_0001_comic_canon_init.py +253 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260511_0001_power_library_init.py +110 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260514_0001_combat_session_ai_provider.py +57 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260528_0001_coordinate_raw_tables.py +148 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260528_0002_coordination_initiator.py +46 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0001_character_def_rename.py +28 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0002_character_instance.py +53 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0003_team_def.py +55 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0004_team_member_def.py +40 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0005_team_instance.py +56 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0006_team_def_real_history.py +36 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0007_team_page.py +73 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0008_team_narrative.py +57 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260529_0009_migrate_org_journal_entries_to_team.py +102 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260530_0002_templates_catalog_columns.py +61 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260530_0003_reconcile_template_category_vocab.py +50 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260531_0001_align_catalog_to_engine_schema.py +44 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260601_0001_build_doc_fields.py +66 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260605_0001_proposals.py +53 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260605_0002_membership_roster.py +62 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260605_0003_xp_award.py +56 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260606_0001_base_drift_guard.py +31 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260607_0001_maneuver_details.py +50 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260609_0001_scene_wall_def_value.py +19 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260609_0002_combat_session_constructs.py +67 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260611_0001_arena_hazard_kind_status_tokens.py +32 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260611_0002_tactic_entry_role.py +28 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260611_0003_seed_archetype_profiles.py +596 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260612_0001_combatant_held_construct.py +29 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260613_0001_combatant_is_hidden.py +27 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260613_0002_combatant_flashed_sense.py +36 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260614_0001_construct_sense_group.py +35 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260614_0002_image_decoy.py +55 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260618_0001_character_extraction_tables.py +74 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260621_0001_combat_session_combatant_slot.py +51 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260621_0002_vpp_power.py +50 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260623_0001_power_categories.py +55 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260726_0001_combat_intent.py +64 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260727_0001_intent_checks_since_form.py +30 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260728_0001_stunned_at_sequence.py +31 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260729_0001_intent_budget_turns.py +20 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260730_0001_standable_structures.py +32 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260731_0001_climb_difficulty.py +25 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260731_0002_widen_character_object_alias.py +60 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260802_0001_identity_by_id_not_xmlid.py +76 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260803_0001_combatant_opening_state.py +89 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260804_0001_combat_random_fight.py +59 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260804_0002_presence_attack_effects.py +69 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260816_0001_template_level_power.py +52 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260831_0001_construct_no_teleport.py +30 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260831_0002_physical_entangle_state.py +32 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260831_0003_character_object_text.py +25 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260902_0001_combatant_in_hero_id.py +53 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260902_0002_object_affects_flags.py +51 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260917_0001_session_names_a_seat.py +84 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260918_0000_drop_combat_tables.py +85 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260918_0001_harness_tables.py +231 -0
- kirby_api-0.1.0/kirby/_migrations/versions/20260918_0002_narration_speaker_id.py +40 -0
- kirby_api-0.1.0/kirby/_migrations/versions/21980c836860_add_asset_id_refs.py +45 -0
- kirby_api-0.1.0/kirby/_migrations/versions/ba28e71c955d_codex_init.py +137 -0
- kirby_api-0.1.0/kirby/admin/__init__.py +1 -0
- kirby_api-0.1.0/kirby/auth/__init__.py +1 -0
- kirby_api-0.1.0/kirby/auth/authz.py +8 -0
- kirby_api-0.1.0/kirby/auth/dependencies.py +17 -0
- kirby_api-0.1.0/kirby/auth/jwt.py +232 -0
- kirby_api-0.1.0/kirby/auth/models.py +32 -0
- kirby_api-0.1.0/kirby/auth/routes.py +30 -0
- kirby_api-0.1.0/kirby/auth/users.py +55 -0
- kirby_api-0.1.0/kirby/builds/__init__.py +9 -0
- kirby_api-0.1.0/kirby/builds/build_doc_store.py +384 -0
- kirby_api-0.1.0/kirby/builds/category_extraction.py +83 -0
- kirby_api-0.1.0/kirby/builds/cli.py +746 -0
- kirby_api-0.1.0/kirby/builds/codex_linker.py +199 -0
- kirby_api-0.1.0/kirby/builds/combatant_loader.py +180 -0
- kirby_api-0.1.0/kirby/builds/concept_to_build.py +505 -0
- kirby_api-0.1.0/kirby/builds/hdc_exporter.py +107 -0
- kirby_api-0.1.0/kirby/builds/importer.py +577 -0
- kirby_api-0.1.0/kirby/builds/json_exporter.py +277 -0
- kirby_api-0.1.0/kirby/builds/json_importer.py +553 -0
- kirby_api-0.1.0/kirby/builds/models.py +842 -0
- kirby_api-0.1.0/kirby/builds/power_categories.py +164 -0
- kirby_api-0.1.0/kirby/builds/power_library_importer.py +625 -0
- kirby_api-0.1.0/kirby/builds/power_library_service.py +258 -0
- kirby_api-0.1.0/kirby/builds/recompute.py +37 -0
- kirby_api-0.1.0/kirby/builds/routes.py +330 -0
- kirby_api-0.1.0/kirby/builds/template_provider.py +249 -0
- kirby_api-0.1.0/kirby/campaigns/__init__.py +1 -0
- kirby_api-0.1.0/kirby/campaigns/_shared.py +36 -0
- kirby_api-0.1.0/kirby/campaigns/membership_service.py +64 -0
- kirby_api-0.1.0/kirby/campaigns/models.py +1153 -0
- kirby_api-0.1.0/kirby/campaigns/routes.py +306 -0
- kirby_api-0.1.0/kirby/campaigns/routes_acl.py +232 -0
- kirby_api-0.1.0/kirby/campaigns/routes_brain.py +392 -0
- kirby_api-0.1.0/kirby/campaigns/routes_documents.py +843 -0
- kirby_api-0.1.0/kirby/campaigns/routes_knowledge.py +711 -0
- kirby_api-0.1.0/kirby/campaigns/routes_membership.py +71 -0
- kirby_api-0.1.0/kirby/campaigns/routes_relationships.py +375 -0
- kirby_api-0.1.0/kirby/campaigns/routes_roster.py +122 -0
- kirby_api-0.1.0/kirby/campaigns/routes_sessions_events.py +448 -0
- kirby_api-0.1.0/kirby/campaigns/routes_xp.py +84 -0
- kirby_api-0.1.0/kirby/campaigns/services/__init__.py +0 -0
- kirby_api-0.1.0/kirby/campaigns/services/brain.py +610 -0
- kirby_api-0.1.0/kirby/campaigns/services/conflicts.py +501 -0
- kirby_api-0.1.0/kirby/campaigns/services/embeddings.py +389 -0
- kirby_api-0.1.0/kirby/campaigns/services/extract.py +234 -0
- kirby_api-0.1.0/kirby/campaigns/services/generation.py +317 -0
- kirby_api-0.1.0/kirby/campaigns/services/legacy_translation.py +208 -0
- kirby_api-0.1.0/kirby/campaigns/services/permissions.py +266 -0
- kirby_api-0.1.0/kirby/campaigns/services/recap.py +158 -0
- kirby_api-0.1.0/kirby/characters/__init__.py +1 -0
- kirby_api-0.1.0/kirby/characters/_shared.py +18 -0
- kirby_api-0.1.0/kirby/characters/build_routes.py +95 -0
- kirby_api-0.1.0/kirby/characters/combat_seed.py +18 -0
- kirby_api-0.1.0/kirby/characters/corpus_copy.py +353 -0
- kirby_api-0.1.0/kirby/characters/models.py +427 -0
- kirby_api-0.1.0/kirby/characters/proposals/__init__.py +0 -0
- kirby_api-0.1.0/kirby/characters/proposals/routes.py +193 -0
- kirby_api-0.1.0/kirby/characters/proposals/service.py +112 -0
- kirby_api-0.1.0/kirby/characters/routes.py +574 -0
- kirby_api-0.1.0/kirby/characters/services/__init__.py +0 -0
- kirby_api-0.1.0/kirby/characters/services/character_info_backfill.py +105 -0
- kirby_api-0.1.0/kirby/characters/services/effective.py +41 -0
- kirby_api-0.1.0/kirby/characters/services/entity_service.py +430 -0
- kirby_api-0.1.0/kirby/characters/services/writeup_persistence.py +88 -0
- kirby_api-0.1.0/kirby/characters/variants_service.py +72 -0
- kirby_api-0.1.0/kirby/characters/xp_service.py +113 -0
- kirby_api-0.1.0/kirby/cli.py +125 -0
- kirby_api-0.1.0/kirby/codex/__init__.py +8 -0
- kirby_api-0.1.0/kirby/codex/cli.py +209 -0
- kirby_api-0.1.0/kirby/codex/deps.py +54 -0
- kirby_api-0.1.0/kirby/codex/models.py +215 -0
- kirby_api-0.1.0/kirby/codex/repositories.py +233 -0
- kirby_api-0.1.0/kirby/codex/routes.py +291 -0
- kirby_api-0.1.0/kirby/codex/schemas.py +97 -0
- kirby_api-0.1.0/kirby/codex/services/__init__.py +1 -0
- kirby_api-0.1.0/kirby/codex/services/embed.py +46 -0
- kirby_api-0.1.0/kirby/codex/services/extract.py +322 -0
- kirby_api-0.1.0/kirby/codex/services/index.py +68 -0
- kirby_api-0.1.0/kirby/codex/services/orchestrator.py +203 -0
- kirby_api-0.1.0/kirby/codex/services/search.py +358 -0
- kirby_api-0.1.0/kirby/codex/services/villain_extractor.py +139 -0
- kirby_api-0.1.0/kirby/codex/services/villain_writeup_linker.py +157 -0
- kirby_api-0.1.0/kirby/codex/workers.py +96 -0
- kirby_api-0.1.0/kirby/combat/__init__.py +47 -0
- kirby_api-0.1.0/kirby/combat/choosers.py +249 -0
- kirby_api-0.1.0/kirby/combat/create.py +327 -0
- kirby_api-0.1.0/kirby/combat/events.py +207 -0
- kirby_api-0.1.0/kirby/combat/harness.py +498 -0
- kirby_api-0.1.0/kirby/combat/models.py +298 -0
- kirby_api-0.1.0/kirby/combat/random_fight/__init__.py +0 -0
- kirby_api-0.1.0/kirby/combat/random_fight/models.py +77 -0
- kirby_api-0.1.0/kirby/combat/random_fight/outcome.py +40 -0
- kirby_api-0.1.0/kirby/combat/random_fight/roster.py +223 -0
- kirby_api-0.1.0/kirby/combat/random_fight/routes.py +176 -0
- kirby_api-0.1.0/kirby/combat/random_fight/runner.py +259 -0
- kirby_api-0.1.0/kirby/combat/random_fight/schemas.py +99 -0
- kirby_api-0.1.0/kirby/combat/random_fight/session.py +313 -0
- kirby_api-0.1.0/kirby/combat/random_fight/worker.py +107 -0
- kirby_api-0.1.0/kirby/combat/routes.py +1122 -0
- kirby_api-0.1.0/kirby/combat/schemas.py +309 -0
- kirby_api-0.1.0/kirby/combat/services/__init__.py +1 -0
- kirby_api-0.1.0/kirby/combat/services/connection_hub.py +73 -0
- kirby_api-0.1.0/kirby/combat/services/publish.py +129 -0
- kirby_api-0.1.0/kirby/combat/services/subscriber.py +121 -0
- kirby_api-0.1.0/kirby/comic_canon/__init__.py +30 -0
- kirby_api-0.1.0/kirby/comic_canon/models.py +342 -0
- kirby_api-0.1.0/kirby/comic_canon/routes.py +385 -0
- kirby_api-0.1.0/kirby/comic_canon/services/__init__.py +0 -0
- kirby_api-0.1.0/kirby/comic_canon/services/archetypes.py +183 -0
- kirby_api-0.1.0/kirby/comic_canon/services/comic_vine.py +215 -0
- kirby_api-0.1.0/kirby/comic_canon/services/embeddings.py +85 -0
- kirby_api-0.1.0/kirby/comic_canon/services/prewarm.py +455 -0
- kirby_api-0.1.0/kirby/comic_canon/services/text_clean.py +28 -0
- kirby_api-0.1.0/kirby/engines/__init__.py +1 -0
- kirby_api-0.1.0/kirby/engines/cost/__init__.py +46 -0
- kirby_api-0.1.0/kirby/engines/cost/hdc.py +200 -0
- kirby_api-0.1.0/kirby/library/__init__.py +1 -0
- kirby_api-0.1.0/kirby/main.py +283 -0
- kirby_api-0.1.0/kirby/media/__init__.py +11 -0
- kirby_api-0.1.0/kirby/media/asset_uses.py +36 -0
- kirby_api-0.1.0/kirby/media/client.py +29 -0
- kirby_api-0.1.0/kirby/media/routes.py +168 -0
- kirby_api-0.1.0/kirby/media/schemas.py +31 -0
- kirby_api-0.1.0/kirby/media/taxonomy.py +33 -0
- kirby_api-0.1.0/kirby/settings.py +113 -0
- kirby_api-0.1.0/kirby/shared/__init__.py +1 -0
- kirby_api-0.1.0/kirby/shared/db.py +69 -0
- kirby_api-0.1.0/kirby/teams/__init__.py +0 -0
- kirby_api-0.1.0/kirby/teams/models.py +209 -0
- kirby_api-0.1.0/kirby/teams/routes.py +234 -0
- kirby_api-0.1.0/kirby/teams/schemas.py +123 -0
- kirby_api-0.1.0/kirby/teams/services/__init__.py +0 -0
- kirby_api-0.1.0/kirby/teams/services/effective.py +63 -0
- kirby_api-0.1.0/kirby/templates/__init__.py +1 -0
- kirby_api-0.1.0/kirby/tenants/__init__.py +1 -0
- kirby_api-0.1.0/kirby/tenants/models.py +89 -0
- kirby_api-0.1.0/kirby/tenants/routes.py +145 -0
- kirby_api-0.1.0/kirby/users/__init__.py +0 -0
- kirby_api-0.1.0/kirby/users/routes.py +40 -0
- kirby_api-0.1.0/kirby/version.py +97 -0
- kirby_api-0.1.0/kirby_api.egg-info/PKG-INFO +273 -0
- kirby_api-0.1.0/kirby_api.egg-info/SOURCES.txt +253 -0
- kirby_api-0.1.0/kirby_api.egg-info/dependency_links.txt +1 -0
- kirby_api-0.1.0/kirby_api.egg-info/entry_points.txt +4 -0
- kirby_api-0.1.0/kirby_api.egg-info/requires.txt +40 -0
- kirby_api-0.1.0/kirby_api.egg-info/top_level.txt +1 -0
- kirby_api-0.1.0/pyproject.toml +296 -0
- kirby_api-0.1.0/setup.cfg +4 -0
- kirby_api-0.1.0/tests/test_teardown.py +99 -0
- kirby_api-0.1.0/tests/test_wheel_contents.py +168 -0
kirby_api-0.1.0/LICENSE
ADDED
|
@@ -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.
|
kirby_api-0.1.0/PKG-INFO
ADDED
|
@@ -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.
|