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/wonByelayerOfDociteram essa lista; MUT-002 inverte só ela. - Duas defesas distintas entre a listagem do repositório e as camadas de
cima (
skill_catalog.go):unprotectedListingimpede que uma listagem do repositório tire uma skill de padrões do catálogo porExcludeListed;catalogResolution.repositoryConfigtira da listagem efetiva (a que os provedores de contexto e os dois montadores de regras leem, no--base, no diff local, no--pre 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 origemrepo. 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):
shadowSameNamesnão roda sem ela,belowPolicydevolve só o repositório,attributeLayerFindingsé identidade sem regra vencida,standardseStandardsActivetêmomitempty, 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.ymlcomLoadCentralPolicy(decodificação com autoridade e mensagens gravadas);LoadLayerDiré a identidade de arquivo que toda camada central compartilha. - O
--prlêconfig.yml, skills eprofiles.ymldo repositório na branch base pela API; a camada de padrões, como a política, vem do checkout local do workflow. --padroestem o alias--standards, como--politicatem--policy;resolveStandardsDirtrata o par comoresolvePolicyDir.- Fontes de skills das camadas centrais num só lugar (
centralSkillSources,skill_catalog.go), usado pelo--basee pelo--pr; o servidor de agente resolve as camadas comresolveCentralLayers(review_layers.go), a mesma sequência dos passosloadConfigeloadStandardsda 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 deconfig.ymlproibido 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:
internal/config/central.gocabeçalho ("The policy governs exactly three things"): agora cita as três camadas delayer.goe as onze seções degovernedSections, com o que cada grupo decide.internal/config/central.godoc deApplyCentralPolicy("exclusive authority over the two fields … Rules and Ignore … only subtracts"):gateeexceptionstambém são substituídos por inteiro; idioma, publicação,analysis_dataequality_gatesquando declarados; o resto pela regra própria.internal/config/central.go("central == nil … the same pointer") einternal/config/config.go(contrato zero-config): reescritos para três camadas — a camada de padrões nunca chega aApplyCentralPolicy, e uma camada central declarada precisa existir.internal/config/config.godoc do pacote ("rule on/off, severity override, ignored paths … controlled exclusively by two things"): as onze seções deConfig,gate.fail_onno config, a política tirandorules/ignore/gate/exceptionsdo repositório.internal/config/inconclusive.go("declares a gate or enables a scanner (SAST, Dependency-Track, analysis data)"):scannerEnabledtambém contadependenciese qualquer engine habilitada.internal/config/governance.go(reviewRepositoryFields: "context, presentation, memory"): as oito chaves listadas.docs/configuration.mdtabela de flags (--politica"precedência sobre rules/ignore/idioma/publicação"): o que a política decide de verdade, e--padroesao lado.internal/config/gate_test.go(TestAUR519GateConfigInconclusive"defaults to "" (no opt-in)"): o teste exercitaresolveInconclusivecom o fallback que o chamador passa; gate declarado valeblock.cmd/aurumcode/aur579_test.go("without a policy, gate.triage: model lets…"): o corpo testa chave ausente (=modeldesde o AUR-608),noneemodel;triage: analysis: modelredundante removido deTestAUR579VerdictFollowsTheGateAfterTriage, com o comentário "gate.triage absent: model (AUR-608)".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_STANDARDSe semstandards_repository: mesma saída, mesma auditoria, mesmoaurum_rules; nenhuma saída gravada de tutorial muda (nenhum tutorial capturareview --help, e os textos da política continuam os mesmos, em inglês). aurum_rulesganhastandardse o valor"standards"emlayersó com a camada;blocking_findingsda 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.mde.board/ficam para a integração. - Limite deste card:
internal/contexté só leitura, entãoskills.Setnão carrega a camada de cada skill; por issolayerOfDocidentifica a camada de padrões pelo prefixo do rótulo (standards/) eunprotectedListingprotege as skills dela da exclusão por listagem do repositório. Um card cominternal/contextempathsdáskills.LayerStandardsao catálogo e remove os dois.