Self-Hosted First

mandatory

Quando uma decisão de stack tech tiver duas opções viáveis — um componente Koder self-hosted e uma alternativa externa — preferir o Koder se ele passar nos 5 gates desta policy para o caso-de-uso específico. A política é algoritmo-estático, dado-dinâmico: os gates e o protocolo vivem aqui; o status por componente vive em `koder.toml [self_hosted]` blocks; um registry auto-gerado agrega; a IA consulta o registry, não esta policy. Subsume a decisão histórica de `web-server.kmd` (Koder Jet) e a Diretriz do Flipping Point do kodec em CLAUDE.md como casos aplicados.

When a tech-stack decision has two viable options — a Koder Stack self-hosted component and an external alternative — prefer the Koder component if it clears all five gates of this policy for the specific case at hand. If any gate fails, the external alternative remains preferred for that case until the gap is closed.

This policy is algorithm-static, data-dynamic: the five gates and the decision protocol live in this file; the per-component status lives in koder.toml [self_hosted] blocks across the monorepo; an auto-generated registry at meta/docs/stack/registries/self-hosted-pairs.md agglomerates them; AI sessions read the registry, not the policy text, when applying the policy.

Sibling of reuse-first.kmd (cross-cutting reuse within the Stack) and code-first.kmd (mechanical- vs-analytical axis). Three orthogonal axes; all three apply at every implementation decision.

When this applies

  • Choosing a tech-stack component for new code that has at least one Koder Stack alternative.
  • Reviewing existing code that currently uses an external dependency for which a Koder alternative now exists.
  • Deciding whether to write a new module in Koder Koda vs RustGoC (Koder Koda is in scope as a self-hosted alternative once the registry lists it for the relevant case).

When this does NOT apply

  • Existing legacy code still using an external dependency. Migration is governed by runtime-lib-first.kmd § Strict SemVer deprecation cycles, not by this policy. This policy applies forward-looking, to new code.
  • Cases where no Koder candidate exists in the registry. The case is outside this policy's reach; the external choice is unencumbered.
  • One-off scripts, test code, build configuration outside the inventory.
  • Hotfixes that cannot wait for a self-hosted component to mature; document the exception and open a backlog ticket toward the Koder alternative for follow-up.

The five gates

A Koder self-hosted component is preferred over an external alternative for a specific case when all five gates pass for that case. Default thresholds are below; a component may tighten them in its koder.toml but cannot loosen them without an RFC amendment.

Gate Question Pass criterion (default)
G1 — Feature parity Does the Koder component cover the case? Required capabilities for this case (FFI, IDE-debug, async, TLS, codec, …) exist and are documented.
G2 — Performance parity Is performance within an acceptable margin? Hot-path: Koder ≤ external × 1.05 on a documented benchmark. Non-hot-path: ≤ × 1.20 with a CHANGELOG note for follow-up.
G3 — Stability Has the component left experimental and held for ≥ 3 releases? Documented in koder.toml [self_hosted].status ∈ {stable, official} and confirmed by koder-spec-audit self-hosted --history.
G4 — Capability gate (per-category) Are the categorical capabilities for this kind of use case present? Per-category sub-gates are defined in the relevant policies/reuse/<category>-first.kmd (UI: a11y baseline + theme contract; runtime-lib: SemVer + tiers; build-tooling: determinism + idempotence; protocol: version negotiation + cross-impl tests).
G5 — Critical-path readiness Is the component proven in production in the Stack? Used by ≥ 2 consuming Koder modules currently in production (not behind a feature flag, not in a smoke-test setup).

The gates are conjunctive (all must pass) and evaluated per case-of-use, not per component globally. The same component may be official for one case (e.g. kodec for mp3) while still gated for another (e.g. kodec for HEVC).

Decision protocol (3 questions answered in chat)

When you are about to write code that picks between a Koder component and an external alternative, before the first Write/Edit call, answer these three questions in your response:

  1. What case am I solving? A one-sentence case-of-use string (e.g. "a TLS-terminating reverse proxy for s.forge vhosts", "a Linux mipmap launcher icon for an Android app", "a high-throughput H.264 decoder for a 4K transcoding pipeline").
  1. Reading registries/self-hosted-pairs.md, is there a Koder component that lists my external alternative under replaces and clears all 5 gates for this case?
    • Yes → use the Koder component. Cite the registry row.
    • Yes for some gates, no for others → name the failing gates explicitly. Either accept the external alternative (and optionally open a promotion ticket on the Koder candidate's backlog to close the gap) or override (and accept the maintenance burden + log it in CHANGELOG).
    • No (no candidate in the registry) → the case is outside this policy's reach; pick the external alternative without ceremony.
  1. Does my decision warrant a CHANGELOG entry or a follow-up ticket?
    • First adoption of a Koder alternative in a high-traffic module → CHANGELOG note.
    • Continued use of an external alternative after gates were closed → ticket recommending migration on next refactor.
    • Discovery of a missing gate on a candidate → ticket on the candidate's backlog to close it.

Corollary — implementing while Koder Koda is pre-1.0

Koder Koda is the Stack's own language and the eventual self-hosted target for most engines. Until it reaches a stable 1.0 it fails G3 (and frequently G2) for production use. Per the protocol above, that does not mean "wait for Koda" — it means: implement the component now in the sanctioned interim language, and treat the Koda version as the maturation track that flips later.

  • Interim language = Go or Rust (the Stack's existing implementation

    languages) — never a new external runtime. Default Go (services, CLIs, engines, web/wasm via GOOS=js); Rust for data-plane, embeddable substrates, and hot-paths. reuse-first.kmd still applies first (extend an existing Go/Rust component before creating one).

  • Register the pair in registries/self-hosted-pairs.md

    with the Koda candidate carried under gates_pending (G3 = "Koda pre-1.0"), so the flip stays tracked and "interim" never silently becomes "permanent".

  • Flip via the promotion pipeline once Koda clears the gates for the case

    (parity + ≥ 3 stable releases + not-slower). This is exactly the kodec model: canonical Rust today, Koda shadow-active until it wins the benchmark.

This is not an exception to self-hosted-first — it is the policy applied to a candidate (Koda) whose G3 is still open.

Data model — koder.toml [self_hosted] block

Components that compete with an external alternative declare their state declaratively:

[self_hosted]
# External alternatives this component substitutes (free-form list).
replaces      = ["nginx", "caddy", "apache", "traefik"]

# Maturity status. Enum: experimental | stable | official | legacy.
#   experimental — public API may change; gate G3 fails.
#   stable       — API stable; gate G3 passes for documented cases.
#   official     — Stack default for the case; new code MUST prefer it
#                  unless an explicit gate fails for the specific case.
#   legacy       — superseded by another self-hosted component; new
#                  code MUST NOT use it.
status        = "official"

# Gates the component currently clears, per-case where relevant.
# Generic gates (no case suffix) apply to all cases.
gates_passed  = [
  "feature_parity",
  "performance",
  "stability",
  "capability:tls",
  "capability:reverse_proxy",
  "capability:hot_reload",
  "production_proven",
]

# Gates known to be missing, with optional ticket / RFC link in the
# value position. Each entry suppresses recommendation for the case
# until the gate is closed.
gates_pending = [
  # "capability:http3 = sites/jet/backlog/pending/047-http3-support.md",
]

# Evidence — where to verify the gate claims.
benchmark_ref   = "infra/net/jet/bench/jet-vs-nginx-2026-03.md"
production_ref  = ["s.forge", "s.poc.vivver.com"]

koder-spec-audit self-hosted --validate <module> checks the schema (gatespassed and gatespending are disjoint; status is in the enum; etc.). Substantive validation (benchmark file exists, production_ref hosts respond) lands in Phase 5 of policies-RFC-002.

Anti-patterns

  • Switching by hype — adopting a self-hosted alternative before its gates close. Cost: regression risk in production code; rework when the candidate's API stabilises late.
  • Switching by inertia — continuing with the external alternative after the gates are clearly met. Cost: dependency drag, security patches, divergence from the reference architecture.
  • Forgetting to declare [self_hosted] block — components clearly competing with an external alternative but missing the block in koder.toml. The anti-drift heuristic (see RFC-002 §3.4) raises a warning when the component name starts with koder-, lives under engines/sdk/ / infra/ / products/dev/, or appears in this policy's precedent list.
  • Benchmark gaming — picking favourable benchmarks for G2 to claim parity. Reviewers verify that benchmark_ref covers the case under test.

Anti-drift heuristic

A component is plausibly a self-hosted alternative if it satisfies any of:

  • module name starts with koder- and is not a product (products/).
  • path is under engines/sdk/, infra/, or products/dev/.
  • name appears in CLAUDE.md § Arquitetura de Referência or in web-server.kmd's precedent list.
  • (Phase 4 onwards) koder.toml declares category ∈ {engine, infra-tooling}.

When the audit finds such a candidate without a [self_hosted] block, it emits a warning. Advisory in Phases 1–3; strict in Phase 5 (per RFC-002 §5).

Promotion pipeline

A pair (Koder candidate, external alternative) may begin life as experimental. As gates close, the maintainer of the Koder candidate updates koder.toml [self_hosted]:

  • experimental → stable: maintainer's call once gates G1–G3 close for at least one case. Document in CHANGELOG.
  • stable → official: requires Stack-level review (Rodrigo or designate). Default is one-PR proposal updating status, plus the relevant gate evidence; review confirms gates clear universally for documented cases.
  • official → legacy: when superseded by another self-hosted component. The deprecating PR also points at the successor in the registry.

The registry is regenerated on every PR that touches any koder.toml [self_hosted] block (CI-enforced from Phase 5).

Applied cases

  • Koder Jet vs nginx / Caddy / Apache / Traefik → see policies/web-server.kmd for the existing decree. Status: official (with documented exception poc-5).
  • kodec vs FFmpeg / GStreamer → CLAUDE.md § Arquitetura de Referência (Flipping Point 2026-04-28). Status: stable for mp3mp4wav; experimental for other codecs.
  • kicon vs ImageMagick / hand-PNGs → built-in to the build-tooling category. Status: official.
  • The full inventory lives in the registry; this policy lists only canonical examples.

Audit

koder-spec-audit self-hosted (binary, in line with code-first.kmd):

  • --report walks every koder.toml, agglomerates [self_hosted] blocks, regenerates registries/self-hosted-pairs.md.
  • --validate <module> checks schema correctness for one component.
  • --gate-check (Phase 5) validates gates_passed claims are backed by evidence.

Cross-references