Modelos de consumo — Taxonomy
consumption specs/consumption/taxonomy.kmd
Define **como um cliente/org consome um componente Koder** como um modelo de dois eixos — **quem opera** (koder × client) × **quem consome** (system × end-user) — e não como uma enumeração de "formas" nomeadas. Cada cruzamento é uma **célula**; cada componente **declara** em que células é oferecido (`koder.toml [consumption]`), e o agregado vive em `registries/consumption-cells.kmd`. Codifica o teste **eixo × atributo**: marca e domínio são **atributos** de deployment (config), não eixos — logo não geram célula nova. Irmão de `specs/variants/taxonomy.kmd`, que faz o mesmo pelo eixo de distribuição.
When this spec applies
Primary triggers
- Perguntar/definir como um cliente consome um produto Koder
- Oferecer um produto Koder sob a marca ou o domínio de um cliente
All triggers
- Perguntar/definir como um cliente consome um produto Koder (hospedado × self-hosted; API × usuário final)
- Oferecer um produto Koder sob a marca ou o domínio de um cliente (white-label)
- Expor um produto Koder hospedado a usuário final com login Koder ID
- Decidir se um componente pode ser hospedado pela Koder quando ele processa dado sensível (PHI/PII)
- Escopar rollout de segurança/auth por conjunto de superfícies
Specification body
Modelos de consumo na Koder Stack
1. Definição
Um modelo de consumo é a combinação de duas escolhas sobre um componente Koder entregue a um cliente/org:
- Operator — quem opera a instância:
koderouclient. - Consumer — quem consome a instância:
system(outro software, via API) ouend-user(uma pessoa, via UI).
O cruzamento é uma célula. Uma célula é o que o cliente contrata. Não é
a variante (isso é surface × target × form factor — specs/variants/taxonomy.kmd),
nem a release (eixo temporal), nem o artefato (build output).
Esta spec não decide quais células a Koder oferece. Ela define o vocabulário e obriga a declaração. Quais células um componente oferece — e por que retém as outras — é dado, declarado no
koder.tomldo componente (§5) e agregado emregistries/consumption-cells.kmd.
2. Por que dois eixos e não uma lista de formas
A pergunta "quais são as formas de usar o Iris?" tinha, em 2026-07-16, quatro
respostas plausíveis dadas de memória — e nenhuma lista fechava. O motivo é
estrutural: as "formas" não eram uma taxonomia, eram amostras de um espaço.
Enumerá-las cai no anti-padrão que a architecture-quality.kmd D3 nomeia
("switch gigante que cresce a cada caso novo"): cada forma lembrada vira um
item novo, e o conjunto nunca é provado completo.
Com os eixos, o espaço é fechado por construção (2 × 2 = 4 células), o caso novo cai numa célula em vez de virar item, e as células vazias ficam visíveis em vez de esquecidas.
3. O teste eixo × atributo (normativo)
Regra R1. Uma propriedade só é eixo se ela muda o artefato ou o contrato. Se ela é o valor de um campo de config, é atributo — e atributo nunca gera célula nova.
O teste é herdado de specs/variants/taxonomy.kmd §3.4, que o aplicou pra
recusar render strategy como 4º eixo ("não muda qual instância o usuário roda"), citando Solidez Arquitetural D9 (porta de mão dupla): identidade
é caminho de mão única; atributo é config, reversível.
3.1 — São eixos
| Eixo | Valores | Por que passa em R1 |
|---|---|---|
operator | koder · client | Muda o artefato: o bundle self-hosted da stack-RFC-027 (kpkg/OCI, binários stripped, gate offline, secrets injetados no install, licença offline assinada) não é o serviço hospedado. Muda também custódia do dado, caminho de update e SLA. |
consumer | system · end-user | Muda o contrato de auth: identidade de sistema (API key) ≠ identidade de pessoa (Koder ID · OIDC, tenant derivado do claim). Determina também se precisa existir variante de UI (web, mobile, …). |
3.2 — São atributos (NÃO eixos)
| Atributo | Onde vive | Por que falha em R1 |
|---|---|---|
| Marca (nome + logo exibidos) | config de deployment | O nome canônico do componente não muda. Precedente ratificado: "the component's canonical name stays Koder Iris… 'Vivver Iris' is a per-deployment presentation layer. One codebase, N brands, zero forks" (imaging#068). É um campo (IRIS_BRAND_NAME, PUT /v1/integration/branding). |
| Domínio (hostname dos endpoints) | vhost (infra/net/jet/sites.toml) | Hostname é config de borda. Uma variante é a mesma variante em qualquer domínio. |
Corolário R1.1 — "white-label" não é um modelo de consumo. Um deployment sob a marca e o domínio do cliente é a mesma célula com dois atributos setados. Não abrir célula, RFC ou modo de produto pra isso. Tornar um atributo configurável num escopo novo (ex.: ACME pra domínio que a Koder não possui) é trabalho de implementação legítimo — mas é trabalho no atributo, não um modelo novo.
4. As células
Id canônico da célula: <operator>-<consumer>.
| Célula | Operator | Consumer | O que é | Auth |
|---|---|---|---|---|
koder-system | koder | system | A Koder hospeda; o sistema do cliente chama a API. O modelo de plataforma da stack-RFC-029 (OpenAPI → SDK gerado → API key). | API key (identidade de sistema) |
koder-end-user | koder | end-user | A Koder hospeda; a pessoa usa a UI e loga com a conta dela no Koder ID. SaaS multi-tenant. | Koder ID / OIDC (tenant do claim) |
client-system | client | system | O cliente opera o bundle stack-RFC-027; o sistema hospedeiro dele integra via SDK. | API key (identidade de sistema) |
client-end-user | client | end-user | O cliente opera o bundle; as pessoas dele logam direto na UI. | Koder ID / OIDC, ou IdP do cliente |
As quatro células são todas significativas — nenhuma é combinação impossível. Célula não-oferecida por um componente é decisão declarada (§5), nunca ausência silenciosa.
Nota — a fronteira do eixo
consumerjá existe em código. No Koder Iris,backend/internal/authn/authn.goverifica o JWT do Koder ID e deriva o tenant do claim (o caminhoend-user), e seuskip()pula/v1/integrationexplicitamente "[it has] its own authentication" — que é o caminhosystem(internal/integration,StaticKeyValidator, uma key por deployment). Os dois modelos de auth são o eixoconsumermaterializado. Esta spec nomeia um contrato que já estava implementado, não inventa um.
5. Declaração por componente (normativo)
Regra R2. Todo componente entregue a cliente/org declara um bloco
[consumption]no seukoder.toml. Célula não-oferecida exige motivo nomeado — "não oferecido" sem motivo é drift, não decisão. Desde astack-RFC-039o ônus é assimétrico: a célula canônica ékoder-*, então é não oferecê-la que exige motivo; oferecê-la não exige justificativa.
Shape (espelha [self_hosted] de policies/self-hosted-first.kmd: declaração
no componente + agregado em registry):
# consumption model — see meta/docs/stack/specs/consumption/taxonomy.kmd
[consumption]
# Cells this component is offered in. Vocabulary: <operator>-<consumer>.
offered = ["client-system"]
# Cells deliberately NOT offered + the named reason. Never silent.
withheld = [
"koder-system = custody-gates",
"koder-end-user = custody-gates",
"client-end-user = no-ui-variant",
]
# O que o componente processa NÃO se declara aqui — vive no bloco [privacy]
# (specs/privacy/posture-schema.kmd R1 data_collected). Os gates do §6 leem de lá.
# Gates de custódia (§6), quando o componente processa dado sensível:
gates_passed = ["G2"]
gates_pending = ["G1", "G3", "G4", "G5"]
# Attributes (§3.2) — configurable per deployment; do NOT open a new cell.
brand_configurable = true
domain_configurable = true
Motivos válidos em withheld (vocabulário fechado):
| Motivo | Significa |
|---|---|
custody-gates | Gated pelo §6 — o componente processa dado sensível e ainda não fechou os gates de custódia. O withheld DEVE vir acompanhado de gates_pending. |
owner-decision | Decisão comercial pendente do owner. |
no-ui-variant | A célula exige uma variante de UI que não existe (specs/variants/taxonomy.kmd). |
no-export-bundle | A célula exige o bundle stack-RFC-027, ainda não produzido. |
not-applicable | O componente não faz sentido nessa célula (ex.: engine embarcável). |
6. Gate de custódia de dado sensível (normativo)
Regra R3. Uma célula
koder-*de componente cujo[privacy].data_collectedinclua categoria sensível (health,biometric,financial) abre quando os 5 gates conjuntivos dastack-RFC-039§4 fecham — não por decisão binária. Enquanto faltar gate:withheld = "<célula> = custody-gates"+ osgates_pendingcorrespondentes.
Os gates (G1 postura declarada · G2 régua jurídica enforçada em código · G3 direitos
do titular ponta a ponta · G4 detecção e resposta · G5 provado sob a postura) são
definidos na rfcs/stack-RFC-039. A forma é a do policies/self-hosted-first.kmd:
conjuntivos, avaliados por caso-de-uso (componente × célula, nunca globalmente),
apertáveis no koder.toml mas não afrouxáveis sem emenda à RFC.
Por que gate e não decisão. A célula koder-* põe a Koder no data path. Pra um
renderizador de mapas isso é trivial; pra um PACS é guarda médica legal — a
policies/clinical-imaging-retention.kmd fixa o piso brasileiro em 20 anos
(CFM Res. 1.821/2007). Um "sim" de owner contrairia essa obrigação de passagem, e um
"não" a adiaria sem condição de saída. O gate converte a pergunta "a Koder custodia dado sensível?" — que não tem resposta estável — em "este componente fechou G1..G5 pra esta célula?", que tem resposta medível e caminho de saída nomeado
(Solidez Arquitetural D12: causa-raiz, não sintoma).
G4 é fail-closed — gate não-provado é gate não-passado. O incidente de 2026-07-11 é a evidência de que presumir verde é como o vazamento acontece: o
JET-231flipou 172 vhosts praauth_gateescopando por domínio e excluiu os não-.koder.dev; o único vhost que servia dado de paciente real (iris.macae-rj.tst.vivver.com, restore do banco do município) estava no domínio do cliente — e foi exatamente o que o rollout pulou: ~8 dias de PHI na internet sem auth. A lição que o notice registra é o teste R1 desta spec com outras palavras: "Rollout de segurança escopado por DOMÍNIO erra justamente as superfícies que mais importam. Escope por o que o vhost serve, não por onde ele mora." Domínio é atributo; o que a superfície serve ([privacy]+ célula) é identidade. Escopar por atributo é o bug.
7. Consequências pro host público
Uma célula koder-* precisa de host público; qual host é decidido pela
stack-RFC-029 §Q1 — fechada como host por produto via Koder Gate. As duas
células koder-* do mesmo componente compartilham o host do produto:
koder-end-user serve a UI, koder-system serve a API sob ele. Isso espelha o
padrão de URL das landings e mantém o atributo "domínio do cliente" (§3.2) como
config de vhost em vez de um sistema à parte.
8. Anti-padrões
- ❌ "Existe uma forma white-label do produto X" → ✅ "X é oferecido em
koder-system; marca e domínio são atributos do deployment" (R1.1). - ❌ Abrir RFC/modo de produto pra uma combinação de atributos.
- ❌ Componente entregue a cliente sem bloco
[consumption](R2). - ❌
withheldsem motivo nomeado — "não oferecemos" calado (R2). - ❌ Escopar rollout de auth/segurança por domínio ou por
environmentem vez de por o que a superfície serve (§6, incidente 2026-07-11). - ❌ Oferecer célula
koder-*de componente com dado sensível em[privacy].data_collectedsem os gates de custódia fechados (R3). - ❌ Declarar o que o componente processa fora do bloco
[privacy]— um lugar só (specs/privacy/posture-schema.kmdR1). - ❌ Presumir um gate verde por falta de evidência — G4 é fail-closed.
- ❌ Inventar um 3º eixo sem passar em R1 e sem RFC (análogo a
specs/variants/taxonomy.kmd§4).
9. Relação com as RFCs existentes
Esta spec é o guarda-chuva que faltava; ela não revoga nenhuma delas:
| RFC | Papel sob o modelo |
|---|---|
stack-RFC-029 | Define como a célula koder-system funciona (API por componente, OpenAPI → SDK, front door). Q1 fechada aqui em §7. |
stack-RFC-027 | Define o artefato que faz operator = client existir (o bundle portátil). |
stack-RFC-031 | Define como a entrega chega ao cliente (patch queue / /k-deliver) — ortogonal aos eixos. |
stack-RFC-039 | Decide o default: a célula canônica é koder-*, e define os 5 gates de custódia que o §6 aplica. Esta spec é o vocabulário; aquela é a decisão (RFC-009). |
stack-RFC-032 | Define a hierarquia de tiers de integração (padrão de domínio → protocolo+SDK → binding). Ortogonal: os 3 tiers dela são todos consumer = system; ela responde como o parceiro fala, esta spec responde quem opera e quem consome. |
10. Adicionar um eixo ou uma célula
Eixo novo exige RFC + passar em R1 (§3). Valor novo num eixo existente
(ex.: operator = partner pra um revendedor que opera pelo cliente) exige RFC.
Motivo novo em withheld (§5) exige edição desta spec. Nenhum dos três é
inferível — todos são decisão registrada.
References
specs/variants/taxonomy.kmdpolicies/self-hosted-first.kmdpolicies/multi-tenant-by-default.kmdpolicies/architecture-quality.kmdrfcs/stack-RFC-027-portable-self-hosted-export.kmdrfcs/stack-RFC-029-external-integration-surface.kmdrfcs/stack-RFC-031-external-delivery-model.kmdrfcs/stack-RFC-032-integration-ports-and-localized-export-bindings.kmdregistries/consumption-cells.kmd