mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Hoist collectUnmappedSrc helper in selectE2eSpecs so unmapped risk-area files are computed before the high-risk early-return. When high-risk wins and risk files lack a manifest entry, the curated result now includes them in unmapped and appends '; curated + risk files without mapping: ...' to reason — mode stays curated. Pin: testAffected gains combined case (migration + unmapped risk file → curated with unmapped in reason). Closes #846.
12 KiB
12 KiB
Impl: OPS86+: curated não engole unmapped-risk sem rastro
Status: rascunho Atualizado em: 2026-08-24 Issue: #846 Intenção: docs/plans/escala-dry-pos-ops86.md Appetite restante: herdado (~2h)
Leitura da intenção
- Outcome: um diff high-risk combinado com arquivo de área de risco sem
entry no manifesto deixa rastro:
selectE2eSpecscomputa os unmapped de área de risco antes do early-return do curated e os expõe nounmappede noreasondo resultado — sem mudar o mode (curatedcontinua vencendo). - O que NÃO negociar: mode
curatedcontinua vencendo ounmapped-risk(a curadoria cobre as superfícies de risco; a rede forte não troca por falso negativo); pinsciSkipInvariants/e2eAffectedManifestintactos; nenhum consumidor muda de semântica (unmappedpopulado em modo curated só soma diagnóstico — ninguém falha com ele). - O que reavaliar:
- O que o
unmappeddo resultado curated deve conter: só os arquivos de risco (filtroE2E_RISK_PREFIXES) ou a lista hoisted completa. Ver D1. - Forma do hoist: helper puro dedicado vs pre-pass inline vs duplicar o loop. Ver D2.
- Wording do
reason. Ver D3.
- O que o
Abordagem recomendada
flowchart LR
F[files + manifest] --> H[collectUnmappedSrc<br/>walk hoisted, semântica do loop]
H --> R[unmappedRisk<br/>filtro E2E_RISK_PREFIXES]
R --> C{high-risk?}
C -- sim --> CUR[curated + reason suffix<br/>unmapped: unmappedRisk]
C -- não --> M[loop: só coleta specs<br/>continue via Set de unmappedSrc]
M --> FR{unmappedRisk?}
FR -- sim --> UR[unmapped-risk fail-closed<br/>idêntico ao de hoje]
FR -- não --> SEL[selected / smoke / none<br/>idêntico ao de hoje]
Opções consideradas:
- D1 — conteúdo do
unmappedno curated: A) sóunmappedRisk(recomendada); B) a lista hoisted completa.- A mantém o pin existente (
testAffected.unit.spec.ts:123-130: high-risksrc/migrations/x.ts→unmapped toEqual([])) —src/migrations/é high-risk, é unmapped no manifesto de fixture, mas não é prefixo de risco; se o curated devolvesse a lista completa, esse pin quebraria. - A preserva a semântica OPS86 de
unmapped= "conjunto descoberto relevante ao fail-closed" (o mesmo conjunto que oreasonnomeia). - A evita ruído de stderr: com a lista completa, todo PR de migration
printaria
[ci-scope] src/ paths with no e2e manifest mapping (selection may not cover them): src/migrations/...num run curated que cobre aquele arquivo por design (label enganosa; o manifesto de produção não mapeiasrc/migrations/de propósito). - B rejeitada: quebra o pin, dilui a semântica e cria ruído em todo diff de schema/lockfile.
- A mantém o pin existente (
- D2 — forma do hoist: A) helper puro
collectUnmappedSrc(files, manifest)module-local com a walk completa, e o loop principal consome a lista hoisted (viaSetnocontinue, sem re-coletarunmapped) — recomendada; B) helper + loop mantém a coleta própria (duas cópias da walk); C) primitiva compartilhadahasManifestMatch(path, manifest)com o loop mantendo a walk própria.- A é a única com UMA fonte de verdade da semântica de walk (skip de e2e
spec deletada, só
src/,startsWithnos prefixes do manifesto) — ounmapped.pushdo loop (:242-244) desaparece, ocontinueviraunmappedSet.has(path), e omanifest.filterrecomputa os matches só para arquivos mapeados (custo trivial: dezenas de arquivos × ~230 prefixes). - B rejeitada: reintroduz exatamente a duplicação que a escala denuncia —
se as cópias divergirem (ex. uma esquece o guard
status === 'D'), o bug ressurge sem teste que o pegue. - C rejeitada: compartilha só o predicado; a walk (quais arquivos contam) continua duplicada; A é estritamente mais limpa.
- Sub-decisão: helper não exportado — um único call site, sem novo
surface público; o comportamento é pinado por
selectE2eSpecs, que é o contrato real.
- A é a única com UMA fonte de verdade da semântica de walk (skip de e2e
spec deletada, só
- D3 — wording do reason: sufixo
; curated + risk files without mapping: <join(', ')>apensado ao texto atual do curated quandounmappedRisknão vazio. Texto-base intocado; sufixo greppável e fiel ao exemplo da intenção. Rejeitada: fundir os arquivos na frase existente (sentence longa, menos greppável). Não há pin do reason do curated (único pin de reason no arquivo::166toContain('dev-mode-only'), branch setup-only), então o wording é contrato-fraco — mas o pin novo casa o substring, então fica semi-pinado.
Recomendação: D1-A + D2-A + D3 — helper puro hoisted, curated devolve só
unmappedRisk com reason-suffix, loop consome a lista hoisted. Nenhum outro
branch muda de comportamento (unmapped-risk/smoke/selected/none idênticos:
as listas são as mesmas por construção — a walk do helper espelha exatamente
:230-247).
Componentes / mudanças
scripts/lib/test-affected-core.mjs— único arquivo de produto:- Novo helper module-local acima de
selectE2eSpecs:function collectUnmappedSrc(files, manifest) { const unmapped = [] for (const { path, status } of files) { if (status === 'D' && E2E_SPEC_PATTERN.test(path)) continue if (E2E_SPEC_PATTERN.test(path)) continue if (!path.startsWith('src/')) continue if (manifest.some((entry) => entry.prefixes.some((prefix) => path.startsWith(prefix)))) continue unmapped.push(path) } return unmapped } selectE2eSpecs(:218): hoist logo no topo —const unmappedSrc = collectUnmappedSrc(files, manifest)econst unmappedRisk = unmappedSrc.filter((path) => E2E_RISK_PREFIXES.some((prefix) => path.startsWith(prefix))).- Early-return high-risk (:219-227): devolve
unmapped: unmappedRiskereasoncom o sufixo (D3) quandounmappedRisk.length > 0. - Loop principal (:230-247):
const unmapped = unmappedSrc;unmapped.push(:242-244) viracontinueviaunmappedSet.has(path)(Set construído da lista hoisted); a coleta de specs continua igual. - Filtro
unmappedRiskde :248-250 e branch fail-closed :251-258: usam ounmappedRiskhoisted (mesma lista, uma cópia do filtro). - Branches smoke/setup-only/none/selected: intocados (a variável
unmappedjá é a lista hoisted; ounmapped: []do branchnone(:273) fica como está — inalcançável com unmapped não vazio, diff mínimo).
- Novo helper module-local acima de
tests/unit/testAffected.unit.spec.ts— pin novo, junto ao bloco curated (:123-130), usando o manifesto de fixture (:88-91, que não tem entry de risco — o caso combinado cai limpo):
(it('high-risk + risk file without mapping → curated com unmapped no reason', () => { const result = selectE2eSpecs( [changed('src/migrations/x.ts'), changed('src/utilities/access/brandNewPolicy.ts')], manifest, ) expect(result.mode).toBe('curated') expect(result.specs).toEqual(E2E_CURATED_SPECS) expect(result.unmapped).toEqual(['src/utilities/access/brandNewPolicy.ts']) expect(result.reason).toContain('curated + risk files without mapping') })src/migrations/x.ts= high-risk via prefixo;src/utilities/access/...= prefixo de risco sem match no manifesto de fixture → unmapped; curated vence.)- Consumidores (
ci-scope.mjs:59-66,e2e-affected.mjs:50-57,run-e2e-affected.mjs:50-61,gate-ci.mjs:89-94): intocados — nenhum falha comunmappedpopulado em curated; o rastro aparece como stderr-warning (label não-risco, correta para o caso) e noreasonimpresso pelorun-e2e-affected. - Manifesto (
scripts/lib/e2e-affected-manifest.mjs),ci-pr.yml,deploy.yml: intocados. Em produção todo prefixo de risco tem entry (pine2eAffectedManifest.unit.spec.ts:67-75), então o caso real só ocorre em drift de manifesto — o pin unitário é o guard. - Migration/schema/UI/Access/Consent: nenhum.
Fases verificáveis
- Núcleo: helper + reordenação em
selectE2eSpecs(D1-A/D2-A/D3) notest-affected-core.mjs. Rodarpnpm test:unit— pins existentes verdes (curated :123-130, unmapped-risk :139-156, deleted-e2e :107-113, reason setup-only :166) + exercitar o caso combinado à mão:node -eimportandoselectE2eSpecscom files sintéticos + manifesto de fixture (espelho do pin novo) e conferindomode/unmapped/reason. - Pin: caso "high-risk + risco não-mapeado → curated com unmapped no
reason" em
testAffected.unit.spec.ts.pnpm test:unitverde. - Gates:
pnpm gate:fast(lint + typecheck + test:unit); opcionalpnpm gate:ci(espelho full local, incl. int/build); changelog:docs/changelog/2026-08-24-ops86-curated-unmapped-rastro.md(entrada curta, append-only); push viapnpm push.
Rabbit holes / Não escopo (engenharia)
classifyTestScope/classifyStaticScope/classifyBuildScope,findUncoveredE2eDomainPrefixes— intocados.- Resultados sintéticos sem merge-base (
ci-scope.mjs:42,e2e-affected.mjs:32) — sem files a classificar, nada a hoist. - Wording do label de warning do
ci-scope/e2e-affectedem modo curated ("selection may not cover them") — cosmético, deixa quieto: o warning agora acerta quando há unmapped de risco de verdade (drift) e nunca aparece nos diffs high-risk normais. seed-posts.mjsunmapped (taxonomia) — fora do caminho e2e.e2eShardConfig(removido no OPS62) — não existe mais.deploy.ymlverify (suíte full) — intocado.- S7 (prefixo
src/utilities/aisem trailing slash over-seleciona futurosrc/utilities/ai*) e S9 (argv dovitest-changed-or-full.mjs) — defers com gatilho do plano de intenção, mantidos. - Não adicionar warn próprio do curated nos consumidores (o
reasonjá carrega o rastro e o stderr genérico cobre).
Riscos e mitigação
- Quebrar o pin curated existente (
:128unmapped toEqual([])): só acontece se a entrega devolver a lista completa em vez deunmappedRisk(D1-B). Mitigação: D1-A justificada no plano + o próprio pin :123-130 roda emsrc/migrations/x.ts(high-risk + unmapped, não-risco) — se o D1-B vazar, o CI falha loud. - Helper divergir da semântica do loop (ex. esquecer o guard de e2e spec
deletada): com D2-A não há segunda cópia para divergir — o loop consome a
lista hoisted; os pins existentes de deleted-e2e (
:107-113) e deleted-src (:115-121) exercitam a walk em modos não-curated. - Diff auto-referencial:
test-affected-core.mjsetestAffected.unit.spec.tsestão emHIGH_RISK_EXACT— o PR desta entrega roda o próprio classificador no CI (curated) egate:ciroda unit full. Mitigação:pnpm test:unit+pnpm gate:fastlocais antes do push. - Custo da walk sempre executada (hoje o early-return pula a walk em high-risk): O(files × prefixes do manifesto), dezenas × ~230 — desprezível; nenhum consumidor mede esse caminho.
- Wording do reason sem pin: o pin novo casa o substring
curated + risk files without mapping; texto-base do curated preservado (nenhum teste depende dele, mas estabilidade evita ruído de diff).
Aceite de engenharia
collectUnmappedSrcmodule-local espelha a walk do loop (skip de e2e spec deletada, sósrc/,startsWithnos prefixes) e é a única fonte deunmappedemselectE2eSpecs.- Early-return high-risk devolve
unmapped: unmappedRisk(só prefixos de risco — pin :123-130 verde) ereasoncom sufixo; curated + risk files without mapping: <join>só quando não vazio. - Branches
unmapped-risk/smoke/setup-only/none/selected com listas idênticas ao comportamento atual (pins :107-161 verdes). - Pin novo "high-risk + risco não-mapeado → curated com unmapped no
reason" em
testAffected.unit.spec.ts(mode/specs/unmapped/reason). ciSkipInvariants/e2eAffectedManifeste consumidores (ci-scope/e2e-affected/run-e2e-affected/gate-ci/ci-pr.yml) intactos.pnpm test:unitepnpm gate:fastverdes; changelog entry curta append-only;pnpm push.