Přeskočit obsah

Spec: Legal apz skeleton (avax-legal-app-template) — pro handoff app-legal

Status: definitivní (2026-06-19). Tahle spec popisuje skeleton legal apz runneru — co to je, jak vzniká, co provisionuje, a hlavně kontrakt s app-legal (co musí dodat app-legal, aby legal aplikace počítala). Čteno pro předání app-legal, ať nedojde k nepřesnosti. Širší apz model: avax-apz.md.


Generický full-stack apz runner pro JAKÝKOLI ekonomicko-právní program (mzdy, daně, exekuce, odstupné…). JEDEN runner, MNOHO programů. Program = data z avaxlegal/apz/ (ne kód); runner je data interpretuje přes embedded compute engine — nikdy nepočítá vzorce sám. Skeleton = tools/avax-legal-app-template; každá legal app je jeho vlastní instance (vlastní Gitea repo + kontejner + compute), vygenerovaná přes admin „Nová legal aplikace" (create_app kind=legal).

2. Tři nosné principy (železně)

  1. Compute = legal pravda. Výpočet dělají legal pluginy (bundly z S3) přes embedded engine (runtime/). Výsledek deterministický, musí se shodovat s app-legal /compute. Golden: cista_mzda_mes @ M:2026-03, hrubá 50000 = 39570.
  2. Read-only supply. Do avaxlegal/apz/ runner NIKDY nezapisuje — plní ji app-legal (compiler). Runner jen čte (POST /apz/sync).
  3. Programy = data, ne kód. Nová apz aplikace ≠ nový kód. Je to nový program.json + plugin bundly publikované do avaxlegal/apz/. Runner napříč všemi appkami identický.

3. Tři úložiště (ekosystém)

Úložiště Co Přístup runneru
Gitea avax-apps/<slug>-app KÓD runner instance čte/píše (in-app Claude: vylepšení, konektory, web)
avaxlegal S3 apz/ legal vzorce + data = pluginy READ-ONLY (compute truth z app-legal)
user/tenant S3 firemní data (vstupy/výsledky) data-bus mezi apps (docházka→apz→účetní)

4. Architektura (co je kde v skeletonu)

backend/avax_apz_backend/
  standalone.py   FastAPI (mount /apz/* za apps-gateway), startup refresh snapshotu z DB cache
  routers.py      /apz/{health, programs, programs/{key}/schema, programs/{key}/run, runs, sync}
  store.py        DBStore — in-memory snapshot z DB cache (apz_programs/apz_subprograms)
  runs.py         DBRunRepo — apz_run_snapshots (audit/reprodukce běhů)
  sync/s3.py      čte avaxlegal/apz/ (index→structure+bundle) → checksum verify → upsert cache
  sync/bundle.py  bundle_format=1 parse + kanonický sha256 checksum
  auth.py         avax-auth (JWKS z core-api) + dev fallback (APZ_DEV_AUTH=1)
  models.py       schéma per-app: SCHEMA=settings.db_schema (app_<schema_slug>) — apz_programs,
                  apz_subprograms (cache) + apz_run_snapshots, apz_external_cache
  config.py       env (DATABASE_URL asyncpg, DB_SCHEMA, LEGAL_S3_*, APZ_SOURCE=db)
  runtime/        COMPUTE KERNEL (engine, run_program, program, plugin_loader, resolved) — vendored
alembic/          migrace (schéma bootstrap v env.py → app_<schema_slug>)
web/              Next.js 15 runner UI (picker → období → form ze /schema → run → dashboard rozpadů)
.gitea/workflows/ backend.yml (scaffold z backend-deploy.yml.example): build→provision .env→deploy→smoke→rollback

Compute jádro runtime/ je posvátné (vendored z app-legal). Neměň logiku výpočtu; jen ho krm daty. Golden 39570 zelený = jádro v pořádku.

5. Co create_app kind=legal provisionuje (automaticky)

  1. apps row + rezervovaný container_port (8101–8199) + health_path=/apz/health (gateway probe).
  2. Gitea repo avax-apps/<slug>-app (push legal template) + secrets (AVAX_AUTH_TOKEN, LEGAL_S3_*).
  3. DB role avax_app_app_<schema_slug> + /etc/avax-apps/<slug>/.env (root:600): DATABASE_URL asyncpg, DB_SCHEMA=app_<schema_slug> (per-app izolace), APZ_SOURCE=db, LEGAL_S3_* (sync). (Provision = CI „first deploy" krok; validuje asyncpg driver → reprovision při starém/cizím .env.)
  4. Subscription + assignment pro publisher firmu (alpha kanál).

Po push do mainCI backend.yml: build image → (1. deploy) provision .env+role → deploy kontejner avax-app-<slug> → alembic (schéma app_<schema_slug>) → smoke /apz/health 200 → auto-rollback. Gateway routing se zapne po prvním zdravém health (auto-enable). První POST /apz/sync (stáhne programy z avaxlegal/apz/) = ruční admin krok (datová operace).

app-legal (compiler) publikuje do avaxlegal/apz/ (read-only pro runner):

apz/index.json                              registr: programs + subprograms + verze + checksum
apz/programs/<key>/structure/v<seq>.json    strukturní verze (nodes/edges/presentation + target|outputs)
apz/subprograms/<key>/v<seq>/bundle.json    plugin bundle (vzorce + parametry, IMMUTABLE)
apz/apps/<slug>/manifest.json               (volitelně) registr app → program + komponenty
  • bundle (bundle_format=1): subprogram_key, version_seq, valid_from/to, variables[] (name/kind/value_type/body/depends_on/…), parameters[], checksum (sha256 kanonický, prázdné pole checksum, sort_keys — stejný algo publisher i runner).
  • structure: program_key, version_seq, valid_from/to, target (format=1) nebo outputs[] (format=2 multi-output), nodes (subprogram|input), edges, presentation (form/breakdown), checksum.
  • Temporální: víc version_seq = paralelní řezy v čase; runner vybere verzi platnou k as_of, gap (chybí verze → ERROR) / drift (zastaralý plugin → WARNING).

Plochý root (subprograms/, programs/) škáluje špatně přes mnoho legal domén → supply se přestaví na segmenty:

apz/catalog.json                            registr segmentů (pro create_app segment-picker)
apz/components/apl-<name>/v<seq>/bundle.json GLOBÁLNÍ komponenty (sdílené napříč segmenty)
apz/segments/<segment>/index.json           per-segment registr: programy + refy na globální components
  • App obsluhuje JEDEN segmentAPZ_SEGMENT v .env (= „svůj compute" dle §2) → syncuje jen ten segment + globální components, co refuje (ne celý root).
  • Paleta = per-app výběr aktivních outputů (checkboxy v UI, stav v app DB) — nabídka = output_variable komponent segmentu (každá komponenta = 1 output), nad multi-output (§7).
  • Checksum + bundle parse BEZE ZMĚNY — komponenty components/apl-* mají identický obsah (a tedy checksum) jako stávající ploché subprograms/apl-* (path-independent → žádný drift). Plochý layout zůstává na S3 jako back-compat.

Dělba práce: app-legal = supply (components + segments + catalog + přestavba compileru/publisheru) · runner skeleton = consumer (per-segment sync sync/s3.py+config.py + paleta API/web) = platform-owned · platforma = create_app čte catalog.json → segment-picker → uloží APZ_SEGMENT do .env.

Přesný JSON kontrakt (v1, ZAMČEN — byte-přesný výstup app-legal dry-run 2026-06-20; supply SSOT: app-legal docs/spec/avax-legal-apz-supply.md §3):

// apz/catalog.json — registr segmentů (wizard segment-picker)
{ "segments": [
    { "key": "mzdy",                            // = APZ_SEGMENT, [a-z0-9-]
      "name": "Mzdová agenda", "description": "…", "status": "active",
      "components": ["apl-cista-mzda", "apl-nemocenske", "apl-exekuce"],
      "programs":   ["mzdova_uctarna"],
      "index": "segments/mzdy/index.json" } ] }
// apz/segments/<seg>/index.json — per-segment registr (runner s APZ_SEGMENT čte)
{ "segment": "mzdy",
  "components": { "apl-cista-mzda": {                  // REFY na GLOBÁLNÍ components/ (sdílené)
      "ref": "components/apl-cista-mzda",
      "output_variable": "cista_mzda_mes", "label_cs": "Čistá mzda",   // paleta: komponenta → 1 output
      "versions": [ { "version_seq": 1, "valid_from": "2024-01-01", "valid_to": null,
                      "checksum": "sha256:…", "key": "components/apl-cista-mzda/v1/bundle.json" } ] } },
  "programs":   { "mzdova_uctarna": {
      "manifest": "segments/mzdy/programs/mzdova_uctarna/manifest.json", "app_slug": "apz-mzdy",
      "versions": [ { "version_seq": 1, "valid_from": "2024-01-01", "valid_to": null,
                      "checksum": "sha256:…", "key": "segments/mzdy/programs/mzdova_uctarna/structure/v1.json" } ] } } }
- Runner (APZ_SEGMENT=mzdy): segments/mzdy/index.jsoncomponents{} (globální bundly z key) + programs{} (structure z key) → checksum verify → upsert cache. key = plný path od apz/; obsah bundle/structure + checksum/parse BEZE ZMĚNY. Extra pole (ref/output_variable/label_cs/manifest/ app_slug/status) = metadata pro paletu/wizard; runner sync je ignoruje (bere jen versions[].key). - Paleta: nabídka = index.components[].output_variable (+label_cs); per-app DB drží aktivní (checkboxy → /run filtruje).

7. Multi-output (structure_format=2) — program = N nezávislých výsledků

Jeden program může mít víc nezávislých outputů (mzdová účtárna = čistá mzda + nemocenské + exekuce) poskládaných z komponent. outputs[] místo single target; každý output = Variable + komponenta + breakdown. Back-compat: structure_format=1 = degenerovaný outputs=[{target}]. Checksum/compute beze změny. /run vrací outputs[], web = dashboard N karet. (Implementováno + 12/12 testů.)

  • app-legal (MAX/compiler): dodá law-based JAK — vzorce, číselníky, proměnné, parametry → zkompiluje + publikuje do avaxlegal/apz/ (supply). Vlastní compute pravdu.
  • in-app Claude (runner): říká CO (které programy, UX, konektory, web) a interpretuje data přes engine. NIKDY nepíše compute (vzorce/sazby/parametry). Zápis do avaxlegal/apz/ = porušení role.
  • Kontrolní bod: POST /apz/programs/<key>/run == app-legal /compute (golden 39570).

9. Gateway + health (důležité pro deploy)

apps-gateway (FastAPI reverse proxy) + auto-enable probe čtou health_path z apps row. Legal app MUSÍ mít health_path=/apz/health (router prefix /apz) — create_app to teď nastaví. Bez toho probe trefí /health → 404 → app „down" → rozbije routing.

10. Gapy opravené 2026-06-19 + známé

Opraveno: - create_app nastaví health_path=/apz/health pro legal (gateway/auto-enable probe). - auto-enable probe používá app.health_path (ne hardcoded /health). - CI provision .env validuje asyncpg driver → reprovision při starém/cizím .env (řeší stale psycopg .env). - template má multi-output paritu (structure_format=2).

Známé / TODO: - delete_app nemaže kontejner / Gitea repo / .env / bucket → ruční cleanup (admin UI dělá jen DB). - Tenancy (schema-per-firma) = další vrstva nad per-app schématem (viz [[reference_avax_tenancy_capability]]).

11. Reference

  • Širší apz model + vyloučené špatné směry: avax-apz.md.
  • In-app Claude kontext: tools/avax-legal-app-template/CLAUDE.md.
  • S3 supply detail: tools/avax-legal-app-template/docs/s3-supply-contract.md.