stack-RFC-021 — Stack Graph: grafo de conexões gerado, glossário e JIT component-aware

accepted

Resolve a causa-raiz da análise superficial das IAs sobre a Koder Stack: não falta "um índice", falta um GRAFO completo e sempre-atual de componentes E das conexões entre eles (hoje deferido no programa #151, travado em dado esparso), e falta um glossário de termos/siglas. Define: (1) o Stack Graph é GERADO por binário (estendendo o `koder-stack-graph` existente) sobre uma SSOT de arestas DECLARADAS em `koder.toml` (sempre-atual por construção, zero drift — Code First); (2) saída dupla de um gerador único — camada de dependências no Graph Explorer (#151, humano) + `stack-graph.json` + digest por-componente (consumo da IA); (3) glossário gerado (termos/siglas × componentes × specs); (4) enforcement por extensão do JIT existente (RFC-014 fase 3) para ser COMPONENT-aware + Regra 11 apontando pro Stack Graph — SEM gate-root novo (tecnicamente impossível em hooks Claude Code e contra o design da RFC-014). Expande o #151 e a RFC-014; não cria mecanismo paralelo.

Status: Accepted — ratificado pelo owner (Rodrigo) em 2026-06-25. Origem: deliberação /k-arch de 2026-06-25 (transcrito nesta sessão). Veredictos fundamentados em code-first.kmd (agregação determinística → binário; anti-padrão default-markdown), architecture-quality.kmd (D6 SSOT-única, D9 reversibilidade, D10 reuso, D11 debuggability) + stack-principles.kmd (#1 Meta First, #2 Quality>Speed, #3 Code First) + reuse-first.kmd.

Naming: o conceito foi inicialmente proposto como "Atlas" e renomeado para Stack Graph — (a) por colisão com o componente existente services/foundation/atlas (Koder Atlas, o produto de mapas/geo), cuja colisão é máxima porque "atlas" significa literalmente "livro de mapas"; e (b) por reuse-first: "Stack Graph" é o nome incumbente (koder-stack-graph + stack-graph.json + cron anti-drift já existem), então esta RFC é a evolução desse gerador, não um artefato novo. (Decisão /k-go/Regra 13, 2026-06-25.)

1. Problema

À medida que a Koder Stack cresce (5 domínios, ~291 folhas, 276 sectors, 100+ componentes nomeados), as IAs desconsideram componentes por analisarem a Stack de forma superficial. A causa-raiz não é "falta um índice" — vários existem. É mais específica:

  1. O dado de conexão é esparso. koder.toml/koder.manifest.json têm campos

    consumesconsumed_bydeps, mas pouco preenchidos → nenhum grafo de conexões pode ser gerado.

  2. Os catálogos são fragmentados e parte manuais → drift (157 deep-dives

    manuais; registry de nomes ~70%, CI cronicamente RED).

  3. As conexões são prosa. "Primary couplings" nos deep-dives é tabela

    markdown legível por humano, não parseável — a IA lê texto solto, não consulta um grafo.

O que não existe: um grafo completo e sempre-atual de componentes E conexões (deferido no programa #151, travado em popular koder.toml); e um glossário de termossiglas (KMDKVGKDBKoda confundem).

O que já existe e é reusado por esta RFC: Graph Explorer live em meta.koder.dev (containment-only — árvore de filesystem, zero arestas); binários geradores em products/dev/koder-tools (koder-stack-graph, koder-module-catalog, koder-stackdoc); RFC-014 (spec-adherence harness) com o JIT jit-spec-inject.sh (fase 3, DONE); e a Regra 11 do /k-go ("consultar a Stack antes de decidir").

2. Non-goals

  • Não cria um markdown/doc novo mantido à mão (seria o 6º artefato

    fragmentado e o 158º a driftar — anti-padrão "default to markdown").

  • Não cria um "gate-root de leitura obrigatória" (ver §5 — tecnicamente

    impossível como bloqueio em hooks Claude Code, e contra o design da RFC-014).

  • Não declara à mão as arestas que o código já prova (import/path): essas são

    mineradas (provenance=mined). Só os acoplamentos NÃO-mineáveis (api/ storeipcdeploy/embed) são declarados (provenance=declared) — nunca disfarçados de provados.

  • Não constrói índice remissivo de termos agora (subsumido por

    grafo+glossário consultáveis; regra dos 3).

  • Não cria um nome-marca/produto novo — Stack Graph é artefato interno de

    infraestrutura (alimenta o site e o JIT), nomeado descritivamente (reuse-first).

3. Decisão 1 (keystone) — Stack Graph GERADO; arestas HÍBRIDAS com proveniência

Veredicto /k-arch (emendado 2026-06-25): gerado, com arestas híbridas minerado+declarado, cada uma marcada com proveniência.

Emenda: a forma original ("declarar todas as arestas em koder.toml") foi corrigida ao descobrir que o koder-stack-graph já minera 165 arestas reais do código, e que seu autor rejeitou — com razão D1 documentada — arestas à mão ("a hand-curated edge would let the graph lie"). A reconciliação honra os dois.

  • O Stack Graph (artefato) é GERADO por binário (estendendo o

    koder-stack-graph existente). Agregação input-fixo→output-fixo = code-first manda virar binário. Sempre-atual, zero drift — é projeção, não cópia à mão.

  • Cada aresta carrega provenance:
    • mined — provada do código (Go import, Dart·Rust path). SSOT = o código;

      nunca à mão (uma aresta-import curada mentiria — D1).

    • declared — acoplamento NÃO-mineável (apistoreipcdeployembed),

      declarado em koder.toml [graph].consumes. SSOT = o koder.toml. Marcada como declarada → nunca se disfarça de provada.

  • Honestidade do grafo = transparência de proveniência, não pureza de fonte.

    Resolve o D1 do autor e torna visíveis as arestas de fio/deploy que a mineração não enxerga (justo as mais relevantes pro plano operador).

Rejeitadas: minerado-só (honesto mas incompleto — mente por omissão das arestas de fio); declarado-só (joga fora as 165 mineradas e reintroduz o risco D1 de driftmentira). Deliberação: `k-arch` 2026-06-25.

4. Decisão 2 — SSOT por proveniência (código × koder.toml)

Veredicto: cada proveniência tem sua SSOT única (D6), sem sobreposição.

  • Mineradas → SSOT = o código (extractEdges: imports Go + path-deps

    Dart/Rust). Não se declaram à mão.

  • Declaradas (só não-mineáveis) → SSOT = koder.toml [graph].consumes do

    componente de origem. É onde [self_hosted]/deps já vivem; população é o gate do #151. Lida no sector-root e nos sub-units (app/, platform/, …).

consumed_by é derivado (inverso das arestas) — nunca declarado. Rejeitado um "arquivo central de grafo" (2ª SSOT → drift, D6).

Schema implementado (flat strings "kind:target" — sem lib TOML, parse-simples):

[graph]
# Só arestas NÃO-mineáveis. import/path vêm do código, NÃO se declaram aqui.
consumes = [
  "api:services/ai/gateway",   # chamada HTTP (sem import)
  "store:infra/data/kdb",      # acesso a store over-the-wire
]

kind ∈ {api, store, ipc, deploy, embed} (não-mineáveis) + import (reservado às mineradas; enum versionado). O gerador funde mineradas ∪ declaradas, dedupando por (source, target, kind), com mined vencendo em colisão exata.

5. Decisão 3 — Enforcement: JIT component-aware, sem gate-root

Veredicto: opção a — estender o JIT existente (RFC-014 fase 3) para ser COMPONENT-aware + Regra 11 apontando pro Stack Graph.

  • O hook jit-spec-inject.sh (PostToolUse, hoje path-aware) ganha um matcher

    path→componente e injeta a vizinhança do grafo (consumesconsumed_by integrations + digest do componente) quando a IA toca/menciona um componente — a fatia certa no momento da decisão.

  • A Regra 11 do /k-go passa a apontar explicitamente: "antes de decidir

    sobre o componente X, consultar o Stack Graph (stack-graph.json / digest de X)".

  • O dente permanece no gate pré-commit (RFC-014) — nada de bloqueio pré-ação.

Rejeitado (b) gate-root de leitura obrigatória: hooks Claude Code não bloqueiam ação (só injetam; UserPromptSubmit/PostToolUse só dão additionalContext); front-load do grafo inteiro (291+ folhas) é caro em token e contra o enxugamento em curso do CLAUDE.md; FP massivo; e fere o design deliberado da RFC-014 (guia injetado × dente no gate). Rejeitado (c) só-Regra-11: é o status quo (a regra existe sem grafo pra consultar — não muda nada).

6. Decisão 4 — Escopo: grafo + glossário gerado

Veredicto: opção b. O gerador emite grafo + glossário da mesma SSOT:

  • Grafo — ataca "ignorar componentes/conexões".
  • Glossário — termossiglas × componentes × specs (KMDKVGKDBKoda/…),

    derivado de component-names.md + vocabulary.md + frontmatter das specs; ataca "ler sigla errado". Gap real e barato.

Índice remissivo de termos: deferido (YAGNI — subsumido por grafo+glossário consultáveis; construir na 3ª necessidade real).

7. Decisão 5 — Consumo: saída dupla de um gerador único

Veredicto: um gerador, uma SSOT, duas saídas:

  1. Visual (humano): estende o Graph Explorer (#151) com a *amada de

    dependências*— fecha o item deferido do #151 (hoje containment-only).

  2. Máquina (IA): emite stack-graph.json (nodes+edges completos) + um

    digest terso por-componente (<component>/.koder-digest.md gerado, ou um índice central) — a forma canônica de consulta da IA, e a fonte de onde o JIT (§5) injeta fatias. A IA consome o JSON/digest, não o visual.

8. Plano (P0–P3)

Fase Entrega Gate
P0 Popular [graph].consumesintegrations nos koder.toml (começar pelos componentes do objetivo Kodeoperador da RFC-020; depois sweep dos 276 sectors). Pode ser dirigido por IA com validação.
P1 Estender koder-stack-graph (binário existente em koder-tools): de containment-only para agregar koder.toml+manifests → stack-graph.json com arestas (consumed_by derivado) + digests. Determinístico, idempotente. P0 (parcial)
P2 Estender Graph Explorer (#151) com a camada de dependências + servir stack-graph.json. P1
P3 Glossário gerado (koder-glossary ou subcomando) + JIT component-aware (jit-spec-inject.sh) + Regra 11 apontando pro Stack Graph. P1

P0 e P1 podem rodar incrementalmente: o grafo é útil já com cobertura parcial, e a cobertura de koder.toml vira uma métrica auditável (advisory → warn).

9. Conformância e auditoria

  • Completude como métrica: koder-stack-graph --check reporta % de

    componentes com [graph] populado (advisory primeiro, per policies/gates.kmd da RFC-014; promove a warn/block conforme cobertura sobe).

  • Consistência (D6): consumed_by derivado garante que não há aresta

    unilateral; o gerador falha se um target referencia componente inexistente no component-names.md (liga as duas SSOTs).

10. Reversibilidade (D9)

  • O Stack Graph é derivado: jogar fora o stack-graph.json e regerar é grátis.
  • O dado declarado ([graph] em koder.toml) é aditivo e versionado (kind

    enum). Porta de mão dupla.

11. Relação com programas/RFCs existentes

  • Programa #151 (Graph Explorer) — esta RFC destrava e expande o item

    deferido (dependency-edges) dele; não cria visualização paralela.

  • RFC-014 (spec-adherence harness) — esta RFC estende a fase 3 (JIT) para

    component-aware; não cria mecanismo de injeção paralelo (respeita a fase 5, subsunção).

  • RFC-015 (Kanon) — o applies_when do Kanon pode futuramente expressar o

    matcher component-aware do JIT (hoje glob simples).

  • component-names.md — o target das arestas referencia os slugs deste

    registry (liga naming ↔ grafo; pode ajudar a destravar o audit RED por uso).

12. Ratificação

Ratificado pelo owner em 2026-06-25. Sub-decisões a confirmar: keystone (Stack Graph gerado / arestas declaradas §3), SSOT em koder.toml (§4), enforcement JIT-sem-gate (§5), escopo grafo+glossário (§6). Ao ratificar, mudar status: draft → accepted e abrir os tickets de P0–P3 no backlog de products/dev/koder-tools (gerador) + meta/context (JIT/Regra 11), cruzando com o programa #151.