Pular para conteúdo

AUR-613 — Camada de padrões de engenharia: segundo repositório central, abaixo da política de segurança

Problema

A arquitetura corporativa mostrada na POC tem três camadas — segurança mantém security-policy, engenharia mantém devops-policy e cada serviço aponta para os dois — mas o produto só conhecia um repositório central (policy_repository / --politica). As skills e os analistas do time de engenharia vinham do próprio serviço, ou de uma pasta dentro do repositório da política, sob a aprovação do time de segurança.

Desenho

A camada é um conceito único: config.Layer descreve cada uma das três camadas (política de segurança, padrões de engenharia, repositório) como dado — se é central, se contribui analistas, o que o config.yml pode declarar, em que idioma erra — e config.LayerOrder declara a ordem uma vez. O que já existia para a política (contenção fora da árvore, carga de skills, time de analistas, auditoria) foi generalizado em vez de copiado.

arquivo responsabilidade
internal/config/layer.go LayerName, LayerOrder (:38, a ordem num só lugar), SectionPolicy (AnySection/NoSection), Layer (:54, com Central, Analysts, Sections, ErrorLanguage) e Layers (:73, as três declaradas); Label/Prefix/ErrorLang; ValidateLayerOutsideReviewedTree (:132), a contenção que toda camada central usa, com insideReviewedTree/resolvePathBestEffort movidos de central.go; LayerDir e LoadLayerDir (:186): .aurumcode/ obrigatório, config.yml conforme Sections, digest.
internal/config/layer_config.go validateLayerConfig (:21): com NoSection, aceita config.yml ausente, vazio ou só comentários e recusa qualquer seção — nomeando as seções encontradas e o que é permitido — em todo documento do arquivo; documentKeys diz o que foi encontrado quando não é mapeamento (sequence, scalar).
internal/config/layer_digest.go layerDigest (:24): sha256 de config.yml, profiles.yml e cada skills/*/SKILL.md, com o caminho relativo antes de cada conteúdo.
internal/config/central.go ValidatePolicyOutsideReviewedTree vira uma linha sobre a contenção genérica; a política tem ErrorLanguage: "en" porque seus erros, anteriores ao catálogo, estão gravados em tutoriais e testes em inglês. Cabeçalho e doc de ApplyCentralPolicy reescritos (ver "Comentários stale").
internal/i18n/catalog.yml Rótulos layer.policy/layer.standards/layer.repository (:46-48 pt-BR), prefixos e substantivos por camada, layer.inside_reviewed_tree (reproduz em inglês o texto atual da política), layer.not_found, layer.config_forbidden (diz o que foi encontrado e o que é permitido), layer.config_invalid, layer.config_not_mapping, layer.load_failed, layer.skill_shadowed e layer.profile_shadowed (nomeiam a camada que perdeu e a que venceu), review.layer_attribution ([%s]).
cmd/aurumcode/review_layers.go centralLayer (:31): uma camada central carregada no comando; resolveStandardsDir (:44, --padroes > AURUMCODE_STANDARDS, vazio explícito é erro de uso); loadCentralLayer (:61): contenção, LoadLayerDir, profiles.yml só se layer.Analysts, origem do workflow (AURUMCODE_STANDARDS_REPOSITORY/_REF) ou local; loadStandards (:95), a etapa que --base e --pr compartilham; skillSource, teamOver (aviso nomeando as duas camadas), audit, activeDir.
cmd/aurumcode/skill_layers.go loadLayerSkills (:34): um carregador para as três camadas, com a tolerância da camada (central falha fechado; repositório descarta só a skill malformada); shadowSameNames (:57): para todo par de camadas em LayerOrder, descarta da camada de baixo a skill cujo nome a de cima declarou, só quando a camada de padrões existe; belowPolicy; layerOfDoc (:129): de que camada veio uma skill selecionada; layeredRules (:142) com merged (de baixo para cima, a camada mais alta sobrescreve) e wonBy.
cmd/aurumcode/skill_catalog.go skillSources por LayerName; resolveSkillCatalog (:27) percorre LayerOrder; resolveSkillRules (:111) monta as fontes e as regras por camada; excludeListedSkills/unprotectedListing (:55, :70): a listagem do repositório não exclui uma skill da camada de padrões de mesmo nome.
cmd/aurumcode/policygate.go dynamicRulesFromCatalog (:111) devolve layeredRules; layerRuleOrigin (:129): política → origem da política, repositório → do repositório, padrões → a origem que o gate da execução aceita (acceptedGateOrigin), por isso o achado conta como fonte skills. mergeDynamicRules substituído por layeredRules.merged (teste AUR-519 adaptado).
cmd/aurumcode/finding_attribution.go attributeLayerFindings (:18): acrescenta [padrões de engenharia] / [engineering standards] à mensagem do achado que cita regra que a camada venceu; sem camada, devolve as issues intactas. Chamado em review_evidence.go:75 antes do snapshot, para que um veredito reaproveitado continue nomeando a camada.
cmd/aurumcode/review_base.go, review_base_inputs.go (:21, :138, :163), review_pr.go, review_pr_inputs.go (:20, :111), review_pr_profiles.go (:35), review_pr_model.go, review_base_analysis.go, review_session.go, pr.go, profiles.go, review_evidence.go, compliance_artifacts.go Flag --padroes (review_base.go:60), standardsDir nas opções e no estado, etapa loadStandardsLayer nas duas fontes, time da camada sobre o do repositório (local no --base, da branch base no --pr), fontes de skills por camada, avisos no terminal, standards na auditoria.
cmd/aurumcode/mcp_gateway.go, mcp_rules.go, internal/mcpserver/gateway.go O servidor de agente resolve AURUMCODE_STANDARDS como o comando; aurum_rules lista cada skill com a camada (policy/standards/repository, layerOfDoc) e standards (omitempty) com o diretório da camada.
internal/reviewprofile/team_layers.go LayerTeams(upper, lower) (:15): analistas da camada de cima sobre os da de baixo, nome igual (sem caixa) da de baixo descartado e devolvido para aviso.
internal/render/audit.go AuditRecord.Standards (:76, omitempty) e AuditStandards{Source, Ref, Digest} (:84), redigidos como os demais campos.
.github/workflows/review.yml Inputs standards_repository/standards_ref (:66), secret AURUMCODE_STANDARDS_TOKEN (:104), step "Checkout engineering standards" (:242: if no input, path: .aurumcode-standards, token: secrets.AURUMCODE_STANDARDS_TOKEN || github.token, persist-credentials: false), --padroes /github/standards (:479) e a montagem :ro com as duas variáveis de origem (:535), ambos sob if [ -n "${AURUMCODE_STANDARDS_REPOSITORY}" ]. Mesmo padrão de shell do bloco da política nesse workflow.
docs/configuration.md, docs/gate-corporativo.md, docs/architecture.md Seção "Camada de padrões de engenharia" com a tabela do que cada camada pode, a ordem, CLI, workflow com os dois repositórios e token; a seção "Padrões de engenharia centrais" do gate corporativo passa a descrever a camada nativa e mantém o caminho por pasta como alternativa; --padroes na tabela de flags; a linha de internal/config cita as camadas.

Decisões:

  • Ordem só em config.LayerOrder: shadowSameNames, belowPolicy, layeredRules.merged/wonBy e layerOfDoc iteram essa lista; MUT-002 inverte só ela.
  • Duas defesas distintas entre a listagem do repositório e as camadas de cima (skill_catalog.go): unprotectedListing impede que uma listagem do repositório tire uma skill de padrões do catálogo por ExcludeListed; catalogResolution.repositoryConfig tira da listagem efetiva (a que os provedores de contexto e os dois montadores de regras leem, no --base, no diff local, no --pr e no MCP) as entradas que nomeiam uma skill que uma camada de cima declara, para que a skill sombreada não volte pela listagem nem vire regra de origem repo. A chave é o que as camadas de cima DECLARAM, não só o que o catálogo derrubou, então a listagem não ressuscita a skill nem quando o diretório do repositório não pôde ser listado (sem o aviso, nesse caso).
  • Sem camada de padrões nada muda (AC-008): shadowSameNames não roda sem ela, belowPolicy devolve só o repositório, attributeLayerFindings é identidade sem regra vencida, standards e StandardsActive têm omitempty, e a política continua errando em inglês (ErrorLanguage).
  • O parecer só rotula o achado da camada de padrões. Rotular também os da política e do repositório mudaria a saída de quem não declara a camada; os rótulos legíveis das três camadas (layer.*) aparecem nos avisos de sobreposição, que só existem com a camada.
  • A política continua carregando o config.yml com LoadCentralPolicy (decodificação com autoridade e mensagens gravadas); LoadLayerDir é a identidade de arquivo que toda camada central compartilha.
  • O --pr lê config.yml, skills e profiles.yml do repositório na branch base pela API; a camada de padrões, como a política, vem do checkout local do workflow.
  • --padroes tem o alias --standards, como --politica tem --policy; resolveStandardsDir trata o par como resolvePolicyDir.
  • Fontes de skills das camadas centrais num só lugar (centralSkillSources, skill_catalog.go), usado pelo --base e pelo --pr; o servidor de agente resolve as camadas com resolveCentralLayers (review_layers.go), a mesma sequência dos passos loadConfig e loadStandards da sessão.
  • Avisos de sobreposição e erros da camada usam um nome só por camada e formas declinadas do catálogo (layer.<camada>.of/.in): skill "x" do repositório ignorada: já existe uma skill com esse nome nos padrões de engenharia, camada de cima; o erro de config.yml proibido diz o que fazer (apagar o arquivo ou deixar só comentários) e o que a camada aceita.

Provas

tests/acceptance/AUR-613.sh (cópia de go.mod go.sum cmd internal pkg e de .github/workflows/review.yml; go test -run por seletor; mutate() com sed e FAIL nomeado; cópia mutada removida ao fim de cada mutação):

id prova última linha
AC-001 TestAUR613StandardsSkillFindingOnBase (en e pt-BR: sem a camada a citação é regra desconhecida; com ela a skill chega ao prompt e o achado sai rotulado), TestAUR613EnvDeclaresLayerAndEmptyFlagIsUsageError, TestAUR613StandardsSkillFindingOnPR (parecer publicado), TestAUR613StandardsSkillThroughAgentServer (aurum_review e aurum_rules), TestAUR613StandardsFindingCountsInTheGate (gate da política e do repositório: reprova, origem skills na auditoria) AUR-613/AC-001/pass
AC-002 TestAUR613StandardsAnalystRunsWhenSelected (sem a camada o analista é desconhecido antes do modelo), TestAUR613StandardsAnalystRunsOnPR, TestAUR613StandardsAnalystShadowsRepositoryAnalyst (aviso nomeia as duas camadas) AUR-613/AC-002/pass
AC-003 TestAUR613ThreeLayersOrderAndAttribution: três diretórios (política com gate, padrões, serviço), skills de mesmo nome entre política/padrões e entre padrões/serviço; o prompt leva só os vencedores, os dois avisos nomeiam as camadas, só o achado dos padrões é rotulado, e no gate da política bloqueiam a regra da política e a dos padrões, não a do serviço. TestAUR613StandardsWinOverRepositorySkill (sem política). TestAUR613ShadowedListedSkillNeverReachesThePrompt (--base e --pr): a skill sombreada que o repositório também lista em review.context.skills não volta pela listagem (texto fora do prompt, aviso presente, achado da regra dos padrões). TestAUR613ListingCannotDropAStandardsSkill: listar o caminho de uma skill de padrões não a tira do prompt nem do gate AUR-613/AC-003/pass
AC-004 TestAUR613StandardsConfigWithSectionIsRefused: gate, ignore, exceptions, rules, quality_gates, publication, dependencies, review; segundo documento YAML; sequência; só comentários aceito; mensagem em pt-BR AUR-613/AC-004/pass
AC-005 TestAUR613StandardsInsideReviewedTreeIsRefused: a própria árvore, subdiretório, caminho relativo, .aurumcode ausente; nenhum prompt capturado AUR-613/AC-005/pass
AC-006 need sobre review.yml e docs; TestAUR613WorkflowChecksOutStandardsLayer: inputs, secret opcional, if do checkout, persist-credentials: false, path, variáveis de origem no step da revisão, --padroes e montagem só sob o guarda AUR-613/AC-006/pass
AC-007 TestAUR613AuditRecordsStandardsLayerOnlyWhenDeclared: sem camada, sem campo; local sem ref; origem do workflow; digest muda com o conteúdo AUR-613/AC-007/pass
AC-008 TestAUR613WithoutTheLayerNothingChanges: com política e repositório (skills de mesmo nome), stdout, stderr, prompt, auditoria e aurum_rules sem nenhum traço da camada; identidades de attributeLayerFindings e loadCentralLayer("") AUR-613/AC-008/pass
MUT-001 Sections: NoSection → AnySection na camada de padrões (layer.go) AUR-613/MUT-001/rejected (TestAUR613StandardsConfigWithSectionIsRefused)
MUT-002 LayerOrder com padrões antes da política AUR-613/MUT-002/rejected (TestAUR613ThreeLayersOrderAndAttribution)
MUT-003 contenção removida em loadCentralLayer (review_layers.go) AUR-613/MUT-003/rejected (TestAUR613StandardsInsideReviewedTreeIsRefused)
MUT-004 catalogResolution.repositoryConfig deixa a listagem crua (skill_catalog.go) AUR-613/MUT-004/rejected (TestAUR613ShadowedListedSkillNeverReachesThePrompt)
MUT-005 unprotectedListing devolve a listagem crua a ExcludeListed (skill_catalog.go) AUR-613/MUT-005/rejected (TestAUR613ListingCannotDropAStandardsSkill)

Valores com forma de segredo não aparecem nos testes deste card; as fixtures são skills, analistas e respostas de modelo sintéticas.

Comentários stale corrigidos

Textos que descreviam o mundo com uma camada só ou com menos seções do que o código tem, corrigidos neste card:

  1. internal/config/central.go cabeçalho ("The policy governs exactly three things"): agora cita as três camadas de layer.go e as onze seções de governedSections, com o que cada grupo decide.
  2. internal/config/central.go doc de ApplyCentralPolicy ("exclusive authority over the two fields … Rules and Ignore … only subtracts"): gate e exceptions também são substituídos por inteiro; idioma, publicação, analysis_data e quality_gates quando declarados; o resto pela regra própria.
  3. internal/config/central.go ("central == nil … the same pointer") e internal/config/config.go (contrato zero-config): reescritos para três camadas — a camada de padrões nunca chega a ApplyCentralPolicy, e uma camada central declarada precisa existir.
  4. internal/config/config.go doc do pacote ("rule on/off, severity override, ignored paths … controlled exclusively by two things"): as onze seções de Config, gate.fail_on no config, a política tirando rules/ignore/gate/exceptions do repositório.
  5. internal/config/inconclusive.go ("declares a gate or enables a scanner (SAST, Dependency-Track, analysis data)"): scannerEnabled também conta dependencies e qualquer engine habilitada.
  6. internal/config/governance.go (reviewRepositoryFields: "context, presentation, memory"): as oito chaves listadas.
  7. docs/configuration.md tabela de flags (--politica "precedência sobre rules/ignore/idioma/publicação"): o que a política decide de verdade, e --padroes ao lado.
  8. internal/config/gate_test.go (TestAUR519GateConfigInconclusive "defaults to "" (no opt-in)"): o teste exercita resolveInconclusive com o fallback que o chamador passa; gate declarado vale block.
  9. cmd/aurumcode/aur579_test.go ("without a policy, gate.triage: model lets…"): o corpo testa chave ausente (= model desde o AUR-608), none e model; triage: analysis: model redundante removido de TestAUR579VerdictFollowsTheGateAfterTriage, com o comentário "gate.triage absent: model (AUR-608)".
  10. internal/review/reviewer.go ("iso25010 scoring remains out of scope"): frase removida; o pacote não existe mais.

Além desses, cmd/aurumcode/skill_catalog.go, mcp_gateway.go, mcp_rules.go e internal/mcpserver/gateway.go deixaram de descrever "two layers"/"policy or repository" onde a camada de padrões entra.

Compatibilidade

  • Sem --padroes/AURUMCODE_STANDARDS e sem standards_repository: mesma saída, mesma auditoria, mesmo aurum_rules; nenhuma saída gravada de tutorial muda (nenhum tutorial captura review --help, e os textos da política continuam os mesmos, em inglês).
  • aurum_rules ganha standards e o valor "standards" em layer só com a camada; blocking_findings da auditoria não muda de forma.
  • Fora do escopo (Non-goals): camada de padrões decidindo gate, ignore, exceções, scanners ou severidade; download pela rede no binário; mais de uma camada de padrões.
  • As duas páginas alteradas não têm capturas (nenhuma referência a docs/assets/capturas); nada pendente. CHANGELOG.md e .board/ ficam para a integração.
  • Limite deste card: internal/context é só leitura, então skills.Set não carrega a camada de cada skill; por isso layerOfDoc identifica a camada de padrões pelo prefixo do rótulo (standards/) e unprotectedListing protege as skills dela da exclusão por listagem do repositório. Um card com internal/context em paths dá skills.LayerStandards ao catálogo e remove os dois.