Internal Documentation internal
TalkIDE internal documentation

Status: Accepted Datum: 2026-05-19 Accepted: 2026-05-19 — accepted by product owner (all Open Decisions OD-1..OD-7 locked; implementation sequencing F1→F2→F3→F4→F5 confirmed) Oblast: Platform doménový model / Multi-tenancy / Hosting / Billing Supersedes: — Rozšiřuje: ADR-015 (per-tenant namespace), ADR-023 §5/§7 (per-tenant-env data-plane, escape hatch), ADR-024 (worker per tenant-environment) Navazuje na: ADR-022 (publish workflow — dnešní implicitní 2 prostředí), ADR-103 (hosting cost allocation / tiers), UC-10009 DESIGN (postpaid hosting)

LOCKED DECISIONS (závazné — product owner schválil 2026-05-19)

Níže uvedená rozhodnutí jsou zamčená a závazná pro veškerou implementaci. Nesmí se přehodnocovat bez explicitního souhlasu product ownera. Detailní implementovatelné UC jsou v UC-10_stripe-billing (F1–F5).

IDRozhodnutíDetail
DP-0AI zůstává prepaid — UC-10007 beze změnyPostpaid se týká výhradně hosting/infra. AI prepaid = jen Anthropic token inference.
DP-1Konfigurovatelný trial_days; hodnota 0 = trial vypnutýtrial_ends_at per tenant (OD-5). Default ~14 dní.
DP-2Separátní hosting_spending_limit_usd — NEreuse UC-10005 spending_limitAI cap a hosting cap jsou nezávislé stropy.
DP-3Dunning: 3 retry (+0/+2/+5 dní) → PAST_DUE → ~7 dní grace → SUSPENDEDFixní rozvrh (ne Stripe-dunning).
DP-5Billing anchor = 1. kalendářního dne v měsíci za předchozí měsícbilling_anchor_day seam ponechán konfigurovatelný (default 1).
DP-6FE estimate = reálná data z hosting_cost_events + non-binding projected end-of-month„Next invoice on 1st” jazyk. UI disclaimer „odhad, ne závazná částka”.
DP-7Hosting je výhradně postpaid — prepaid back-door zrušen (2026-05-23)Prepaid back-door (hosting_credit_usd, hosting_credit_initial_usd, settlement_mode, source enum hodnoty TOPUP/AUTOPAY/TRIAL_INCLUDED) odstraněn v be#142 (Liquibase 0050+) a fe#38 (FE HostingCreditPanel → HostingBillingPanel). Důvod: dormant artefakty mátly agenty + UI/UX. Návrat k prepaid by vyžadoval nový design a nové DB migrace, ne reaktivaci. Sourozenecké issues: be#142, fe#38.
OD-1Hosting spend cap agregovaný přes všechna prostředí (jeden cap per tenant, ne per-env)Per-env cap jako aditivní seam v budoucnu.
OD-2Při neuhrazené infra faktuře po PLNÉ dunning sekvenci → hard scale-to-zero CELÉHO tenant namespaceTenant-wide enforcement. Aktivuje se až ve F4. F1–F3 = soft enforcement pouze (notifikace, žádný suspend). Platí i pro default „TalkIDE” prostředí.
OD-3Nové prostředí default resource_mode = SHAREDDEDICATED = vědomá opt-in volba, gated plánem.
OD-4Žádný DEDICATED v alfě — F5 (DEDICATED) plně post-alfaDEDICATED provisioning je operačně těžký; alfa (1–10 tenantů) ho nepotřebuje.
OD-5Trial granularita per tenant (NE per-env — abuse vektor)Jedno trial okno celkem; po vyčerpání všechna prostředí přejdou na metered.
OD-6Jedna měsíční infra faktura per tenant s rozpadem na per-environment řádkyKonzistentní s DP-5 (jeden anchor per tenant).
OD-7Pořadí fází F1→F2→F3→F4→F5 potvrzenoF0/UC-10008 je nezávislé (kompletní). F4 (rizikový ns cut-over živého usera) až po ověření mechaniky na throwaway prostředích (F3).

NEsuperseeduje žádné existující ADR. Environment je nadřazená doménová abstrakce, do které se existující koncepty (tenant namespace, per-tenant-env data-plane, worker-per-tenant-env) zasazují — nikoli je ruší. ADR-015, ADR-023, ADR-024 zůstávají platné a autoritativní pro své vrstvy; tento ADR jim dává společný doménový rámec a explicitní user-facing entitu.


Context

Co je dnes „environment-like” implicitně

TalkIDE dnes nemá entitu Environment. Místo toho existují dvě implicitní prostředí odvozená ze sloupce builds.env ∈ {dev, prod} (ADR-022 §1, §4):

Implicitní prostředíURLTriggerDBNamespace (dnes)
Preview (env=dev)<uuid>.talkide.appkaždý Mara commitper-deploy schematenant-{slug} (ADR-015 §1)
Published prod (env=prod)<slug>.talkide.appexplicitní Publishpersistent schematenant-{slug} (tentýž ns)

Faktický stav (ověřeno z ADR-015 §1, ADR-022 §4, ADR-023 ř.460):

  • Namespace model = tenant-{slug}, JEDEN namespace per tenant. Preview i published prod pody dnes běží ve stejném tenant-{slug} namespace; liší se jen hodnotou builds.env a hostem ingressu.
  • mirek-dev / mirek-prod (namespace-per-tenant-env) z ADR-024 §1 a ADR-023 §5 je separátní budoucí workstream, dnes NEaktivní (ADR-023 ř.460: „namespace per tenant-environment je separátní workstream”). Worker extrakce (ADR-024) na něm závisí jako na substrátu.
  • Data-plane (ADR-023): schema-per-app v jedné sdílené talkide_dataplane DB; per-app schema klíčované (slug, env) → K8s Secret app-{slug}-{env}-db. ADR-023 §7 escape hatch = dedikovaný DB cluster per tenant-environment jako premium plán (NEimplementováno).

Driver tohoto ADR (zadání usera)

User chce Environment jako first-class entitu vedle Project a Domain:

  • User si vytvoří libovolný počet prostředí. Prostředí = MÍSTO, kde reálně běží infrastruktura usera (compute + db + storage).
  • Každý user má 1 výchozí nesmazatelné prostředí „TalkIDE” = sdílená infrastruktura, kterou máme dnes (sdílený k8s cluster, sdílený DB cluster, sdílené storage). V „TalkIDE” prostředí běží Preview + talkide-worker (AI runtime, ADR-024).
  • Další prostředí (TEST, PROD, …) si user vytvoří a u každého zvolí, zda resources (compute, db, storage) běží shared nebo vlastní (dedicated) — a s jakou konfigurací.
  • Plný multi-env hned (user explicitně: vytváření více prostředí + výběr shared/dedicated + konfigurace — ne minimální řez).

Dvě billing vrstvy (potvrzeno userem — viz UC-10009)

VrstvaModelCo pokrývá
AI prepaidprepaid topup (UC-10007 živé)Anthropic token spend (samotná inference)
Infra postpaidmetered akruál → měsíční Stripe fakturacompute/db/storage per prostředí, včetně podu, kde běží talkide-worker + Preview

Důsledek: dřívější DP-4 intra-namespace „hosting vs ai-dev” label-split (be#125) se z velké části rozpouští — attribution je per-prostředí (≈ per-namespace; OpenCost aggregate=namespace to umí nativně). Viz sekce „be#125 disposition” níže a UC-10009 revize.


Decision

1. Environment jako first-class entita

Zavádí se nová doménová entita environment (uživatelsky „Prostředí”), rovnocenná project a (budoucí) domain. Environment patří tenantovi (konzistentní s ADR-015: namespace patří tenantovi, ne uživateli; v alfě 1 tenant ≈ 1 user).

tenant (EXISTUJE)
  └─ environment (NOVÁ)  1:N
        ├─ kind            DEFAULT | USER_CREATED
        ├─ name            "TalkIDE" (default) | user-zvolené ("TEST","PROD",…)
        ├─ slug            env-slug (RFC-1123, unikátní v rámci tenanta)
        ├─ resource_mode   SHARED | DEDICATED
        ├─ status          ACTIVE | SUSPENDED | DEPROVISIONING
        ├─ config          (JSON/struct — sizing, db tier, storage tier; sekce 4)
        ├─ deletable       BOOLEAN  (false pro DEFAULT „TalkIDE")
        └─ created_at / updated_at

project (EXISTUJE) ─── N:M (přes deployment-target) ──→ environment
domain  (EXISTUJE/budoucí) ─── N:1 ──→ environment

Vztah Project → Environment. Projekt se nasazuje do prostředí. Dnešní implicitní model (preview + published prod) se mapuje na:

  • Preview = nasazení projektu do prostředí „TalkIDE” (default, shared), env=dev ekvivalent.
  • Published prod = nasazení projektu do prostředí, které si user zvolí jako prod cíl Publishe — defaultně může zůstat „TalkIDE”, nebo user-created prostředí (TEST/PROD, shared nebo dedicated).

Projekt může mít více aktivních nasazení (preview v „TalkIDE” + published do „PROD”) současně — to formalizuje dnešní paralelní preview+prod (ADR-022), nově ale prod cíl je volitelné prostředí, ne fixně „tentýž tenant-ns”.

Vztah Domain → Environment. Doména (custom doména / <slug>.talkide.app) směřuje na konkrétní prostředí. <uuid>.talkide.app (preview) → „TalkIDE”; <slug>.talkide.app (published) → prod cílové prostředí. Custom domény (budoucí) se navážou na zvolené prostředí.

2. Default nesmazatelné prostředí „TalkIDE”

Každý tenant má právě jedno prostředí kind=DEFAULT, name="TalkIDE", resource_mode=SHARED, deletable=false. Vzniká lazy (konzistentní s ADR-015 §6 lazy namespace) při prvním úkonu, který prostředí vyžaduje (první Create Project / první Publish), NE při registraci.

  • „TalkIDE” = přesně dnešní sdílená infrastruktura: sdílený K8s cluster, sdílený talkide_dataplane DB cluster (ADR-023 §4), sdílené NFS/Spaces.
  • V „TalkIDE” běží Preview (<uuid>.talkide.app) a talkide-worker (ADR-024 AI runtime) — tedy AI vývojový loop má vždy domov ve sdíleném default prostředí, které nelze smazat ani suspendovat za neplacení infry (viz Open Decision OD-2 — finální chování default env při neplacení rozhoduje user).
  • deletable=false je tvrdý invariant na BE i FE (Delete Environment UC odmítne DEFAULT s 409 CONFLICT_ENVIRONMENT_NOT_DELETABLE).

3. Namespace mapping — Environment ⇒ namespace

Tohle je most mezi novou entitou a existujícím ADR-015 / ADR-023 §5 / ADR-024.

Cílový invariant: jedno prostředí ⇒ jeden K8s namespace (jednotka kvóty, domov worker podu, domov build/test Jobů, jednotka cost attribution).

ProstředíCílové namespace jménoVztah k existujícím ADR
„TalkIDE” (DEFAULT, SHARED)tenant-{slug} (beze změny dnes) → cílově {tenant}-talkide (env-suffixed)ADR-015 §1 dnes; ADR-024/ADR-023 §5 per-tenant-env je cílový tvar
USER_CREATED, SHARED{tenant}-{env-slug} (per-tenant-env ns ve sdíleném clusteru)aktivuje ADR-024 §1 / ADR-023 §5 namespace-per-tenant-env
USER_CREATED, DEDICATED{tenant}-{env-slug} v dedikovaných resources (vlastní DB cluster, ResourceQuota dle config)realizuje ADR-023 §7 escape hatch jako user-facing produkt

Klíčové pozorování: Environment koncept aktivuje namespace-per-tenant-env split z ADR-023 §5 / ADR-024 — to, co bylo „separátní budoucí workstream”, dostává doménový owner (entita Environment) a user-facing důvod existence (user si prostředí explicitně vytváří). Migrace ze současného tenant-{slug} (jedno ns, oba env) na per-environment ns je fázovaná (sekce „Migrační plán”).

Naming bezpečnost: env-slug RFC-1123 validace (analogie talkide-be#44 pro tenant slug). Délka {tenant}-{env-slug} musí zůstat ≤ 63 (K8s limit) — env-slug proto kratší limit (např. ≤ 20). Rezervovaná env-slug: talkide (default), plus reserved slug list (CLAUDE.md DO konvence).

4. Provisioning model — shared vs dedicated + konfigurace

AspektSHAREDDEDICATED
Computesdílený K8s cluster, namespace + ResourceQuota dle plánu (ADR-024 §5)sdílený nebo (premium) dedikovaný node pool; ResourceQuota dle env config
DBschema-per-app v talkide_dataplane přes PgBouncer (ADR-023 §4, invariant §6)dedikovaný DO Managed PG cluster per environment (ADR-023 §7 escape hatch)
Storagesdílený NFS subdir + sdílený Spaces prefixdedikovaný PVC / Spaces bucket (config)
Provisionerreuse existující (NamespaceProvisioner, ProjectDatabaseProvisioner, ADR-023 schema-per-app)rozšíření: per-env DB cluster provisioning (ADR-023 §7), navázáno na env config

Provisioning je idempotentní get-or-create per resource (konzistentní ADR-015 §8) a lazy (vzniká při prvním nasazení projektu do prostředí, ne při Create Environment — nebo eager pro DEDICATED, kde je provisioning těžký; finální granularita = Open Decision OD-3/OD-4).

EnvironmentProvisioner wrapper (interface + K8s impl + Noop) — stejný vzor jako ADR-014/ADR-015 (žádná výjimka z patternu). Provisioning failure → výjimka → @Transactional rollback (ADR-015 §11).

Konfigurace prostředí (config) drží minimálně: sizing (ResourceQuota třída / plán), DB tier (shared schema | dedicated cluster + velikost), storage tier. Granularita config v alfě = Open Decision OD-4 (přednastavené třídy vs. volné parametry) — nerozhoduje tento ADR.

5. Dvě billing vrstvy — per-environment attribution

VrstvaAtribuceMechanismus
AI prepaidper user/tenantUC-10007 topup + UC-10008 marže (beze změny — DP-0 LOCKED)
Infra postpaidper environment (≈ per namespace)C.2 OpenCost aggregate=namespace → mapování namespace → environment → tenant → měsíční Stripe faktura (UC-10009 postpaid)
  • talkide-worker + Preview běží v „TalkIDE” prostředí → jejich infra cost je infra-postpaid náklad prostředí „TalkIDE” (transparentně na faktuře), NIKOLI AI prepaid. AI prepaid pokrývá jen Anthropic token inference.
  • Každé další prostředí (TEST/PROD) je samostatná řádka na „Hosting/ Infrastructure” faktuře — user vidí cost per prostředí.
  • Cost attribution = OpenCost aggregate=namespace (nativní, žádný intra-namespace label-split potřeba), protože jedno prostředí = jeden namespace (invariant sekce 3). Tím se rozpouští velká část be#125 (viz níže).

6. be#125 disposition (intra-namespace label-split)

Rozhodnutí: be#125 z velké části PADÁ, zůstává RESIDUUM jen pro přechodné období a unknown-workload safety net.

Zdůvodnění:

  • be#125 / UC-10009 DP-4 V1 (label-aware C.2 aggregate=namespace,label: talkide.io/workload-class) řešilo problém „worker + build Joby + user-app pody jsou ve stejném tenant-{slug} ns, nelze je cost-rozlišit”.
  • Pod Environment modelem (sekce 3 invariant) je každé prostředí vlastní namespace. talkide-worker + Preview žijí v „TalkIDE” ns; published prod app žije ve svém prod env ns. Cost je tedy přirozeně atribuovatelný aggregate=namespace (přesně to, co ADR-024 Consequences slibuje: „per-tenant izolace + billing attribution … bez heuristik”).
  • Nově není potřeba intra-namespace label-split, protože „hosting vs ai-dev” hranice = hranice prostředí = hranice namespace. Worker compute není „misattribuovaný hosting” — je to legitimní infra-postpaid náklad prostředí „TalkIDE” (user ho vidí transparentně, platí ho jako infra postpaid, ne jako AI prepaid). Tím mizí samotný problém, který be#125 řešil.

Reziduum (zůstává):

  1. Přechodné období — DOKUD je živý prod na starém tenant-{slug} modelu (jedno ns, preview+prod pohromadě) a DOKUD nepřistane ADR-024 worker do per-env ns, je aggregate=namespace na tenant-{slug} ns stále hrubé (smíchá preview+prod). Reziduální be#125 = dočasný label-split nebo namespace-split pouze pro období migrační Fáze 2–4 (viz migrační plán), než je per-environment ns invariant plně živý. Po dokončení migrace reziduum odpadá.
  2. Unknown-workload safety net — i v cílovém stavu má smysl mít v C.2 explicitní handling „namespace, který se nemapuje na žádné environment” (orphan ns) → WARN + metrika + konzervativní non-charge (NEfakturovat neznámé na usera). To je jednoduchý guard, ne plný label-split.

Návrh textu pro aktualizaci GitLab issue be#125 — viz UC-10009 revize FEEDBACK a sekce „be#125 issue update” níže (PM přenese do GitLab, tento dokument GitLab nevolá).


Consequences

Pozitiva

  • Jeden srozumitelný mentální model pro usera — „prostředí = kde to běží”; preview/prod přestává být skryté kouzlo builds.env.
  • Billing attribution bez heuristik — jedno prostředí = jeden namespace = jedna cost-řádka; be#125 label-split z velké části odpadá.
  • Aktivuje a zarámuje rozjeté workstreamy — ADR-024 worker-per-tenant-env a ADR-023 §5/§7 dostávají doménového ownera a user-facing důvod; nic se nesuperseeduje.
  • Premium izolace jako produkt — ADR-023 §7 escape hatch (dedikovaný DB cluster) se stává user-volitelnou resource_mode=DEDICATED, ne ad-hoc ops.
  • Decoupling AI ↔ infra zůstává čistý — AI prepaid (UC-10007) a infra postpaid (UC-10009) jsou dvě nezávislé vrstvy; default „TalkIDE” garantuje, že AI vývoj má vždy domov.

Rizika a omezení

  1. Migrace živého produkčního usera. popelkam / todo-list.talkide.app běží na tenant-{slug} modelu. Migrace MUSÍ být fázovaná a non-disruptive (viz migrační plán) — entita Environment se zavádí nejdřív jako záznamová abstrakce nad existujícím stavem (default „TalkIDE” namapuje na současný tenant-{slug} ns) a teprve postupně se přepíná na per-env namespace.
  2. Per-environment namespace = víc K8s objektů. ResourceQuota, RBAC, secret replikace (ADR-015 §4–6) per prostředí, ne per tenant. Connection budget invariant (ADR-023 ř.443) je per (db,user) — víc prostředí = víc per-app rolí. Hlídat monitoringem; škálování tenantů/prostředí = trigger pro vyšší DB tier (ne automatika).
  3. DEDICATED provisioning je těžký a drahý. Dedikovaný DO Managed PG cluster per environment má reálnou $ a čas cenu. Alfa: omezit počet DEDICATED prostředí / gate plánem (Open Decision OD-3/OD-4).
  4. Default env při neplacení infry — nesmazatelné „TalkIDE” nelze za neplacení smazat; co přesně se s ním stane (suspend Preview? grace? nikdy nesuspend?) je Open Decision OD-2, NErozhoduje tento ADR.
  5. Naming / délkový limit{tenant}-{env-slug} ≤ 63; env-slug validace + reserved list nutné před public alpha (analogie be#44).

Alternatives Considered

AlternativaDůvod odmítnutí
Ponechat implicitní 2 prostředí (builds.env)Neumožní user-created prostředí, shared/dedicated volbu ani per-env billing — neplní zadání.
Environment = jen UI label nad builds.envNedá first-class entitu, vztahy Project→Env / Domain→Env, ani per-namespace billing attribution. Polovičaté.
Environment = synonymum tenant (1 ns = 1 env)Rozbíjí ADR-015 (namespace patří tenantovi; jeden tenant má víc prostředí). Tenant zůstává vlastník, Environment je N pod tenantem.
Big-bang migrace na per-env namespaceShodí živého prod usera (popelkam/todo-list). Odmítnuto — povinný fázovaný plán.
Zachovat be#125 plný label-split i v cílovém stavuZbytečná složitost — per-environment ns dává nativní aggregate=namespace attribution. Reziduum jen pro přechod + orphan safety net.

Implementation Notes

Vztah k existujícím ADR (explicitní)

ADRVztahCo se mění / nemění
ADR-015Rozšiřujenamespace stále patří tenantovi; nově {tenant}-{env-slug} místo tenant-{slug}. Provisioner pattern, idempotence, lazy, error handling — beze změny. NEsuperseeduje.
ADR-023 §4/§6Beze změnydata-plane separace + pooler invariant platí. Schema-per-app klíč rozšířen (slug, environment).
ADR-023 §5Aktivujenamespace-per-tenant-env přestává být „separátní workstream” — Environment je jeho doménový owner.
ADR-023 §7Realizuje jako produktescape hatch dedikovaný DB cluster = resource_mode=DEDICATED. NEsuperseeduje.
ADR-024Zarámujeworker-per-tenant-environment ns = worker žije v prostředí „TalkIDE”. Substrát (per-tenant-env ns) je teď doménově vlastněn Environment. NEsuperseeduje.
ADR-022Formalizujeimplicitní preview(env=dev)/prod(env=prod) → nasazení projektu do prostředí. ProjectStatus state machine beze změny; jen prod cíl je volitelné Environment.
ADR-103Beze změny, autoritativnítier/quota struktura platí; tier se nově může vázat per environment (config), ne jen per account.
UC-10009Přerámujepostpaid hosting = postpaid per environment; DP-0..DP-6 LOCKED zachovány, DP-4 přerámováno (sekce „be#125 disposition” + UC-10009 revize).

Pořadí (foundation → navazující) — detail v migračním plánu

  1. Foundation: entita environment + lazy default „TalkIDE” namapované na současný tenant-{slug} ns (čistě záznamová vrstva, žádná infra změna, žádný dopad na živého usera).
  2. Billing per-environment napojení (UC-10009) — atribuce přes mapování ns→environment (zatím 1 ns „TalkIDE” = celý dnešní tenant-{slug}).
  3. Create/Manage Environment UC (user vytváří USER_CREATED SHARED prostředí).
  4. Per-environment namespace cut-over (aktivace ADR-023 §5 / ADR-024 substrátu).
  5. DEDICATED resource_mode (ADR-023 §7 jako produkt).
ReferenceVztah
ADR-015 / ADR-023 §5,§7 / ADR-024rozšiřované / aktivované / realizované vrstvy
UC-10009 DESIGN (postpaid)přerámováno per-environment (revize na téže branch)
be#125z velké části padá; reziduum přechod + orphan safety (viz disposition)
talkide-be#44env-slug RFC-1123 validace = analogie tenant slug validátoru

be#125 issue update (návrh textu pro PM — NEvoláno GitLab)

Re-scope (ADR-026 Environment first-class): Původní problém be#125 (worker

  • build Joby + user-app pody nerozlišitelné v jednom tenant-{slug} ns → aggregate=namespace míchá hosting a ai-dev) z velké části zaniká. Pod ADR-026 je každé prostředí vlastní namespace; talkide-worker + Preview žijí v prostředí „TalkIDE” a jejich compute je legitimní infra-postpaid náklad toho prostředí (transparentně na faktuře), ne misattribuovaný hosting. aggregate=namespace → namespace→environment→tenant je nativní attribution bez label-splitu.

Zbývá (reziduum, zúžený scope):

  1. Přechodné období — než per-environment ns invariant (ADR-026 migrace Fáze 4) plně přistane a než ADR-024 worker přejde do per-env ns, je aggregate=namespace na starém tenant-{slug} ns hrubé. Dočasný label-split NEBO namespace-split jen pro období migračních Fází 2–4. Po dokončení migrace tento bod odpadá (uzavřít).
  2. Orphan-namespace safety net (trvalé, jednoduché) — C.2: namespace nemapující se na žádné environment → WARN + metrika + NEfakturovat usera (konzervativní non-charge). Není to label-split, je to guard.

Out of scope (přesunuto do ADR-026 / UC-10009 revize): plná per-environment billing attribution, DP-4 enforcement rozsah, dedicated resource_mode. Issue zúžit na reziduum výše; po migraci Fáze 4 zvážit close.


Was this page helpful?

Thanks for the feedback.