ci(quality): enforce stale-allowlist on check-known-symbols (Fase 6A.3 follow-up) (#3899)

Integrated into release/v3.8.26
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-15 14:10:58 -03:00
committed by GitHub
parent f8131c8a19
commit be82146d37
2 changed files with 95 additions and 9 deletions

View File

@@ -12,7 +12,8 @@
// (2) COMBO STRATEGIES — a cadeia de despacho `strategy === "..."` em
// open-sse/services/combo.ts DEVE tratar exatamente o conjunto canônico de
// ROUTING_STRATEGY_VALUES (src/shared/constants/routingStrategies.ts), exceto
// as estratégias-default implícitas (priority não tem branch; cai no
// as estratégias-default implícitas documentadas em IMPLICIT_DEFAULT_STRATEGIES
// (estratégias canônicas sem NENHUMA referência `strategy === "..."`; caem no
// ordenamento padrão). Adicionar um valor canônico sem fiá-lo no despacho, ou
// fiar uma string de estratégia que não é canônica (inventada), falha aqui.
//
@@ -38,10 +39,21 @@
// Catraca: cada divergência pré-existente fica numa allowlist documentada e sai 0 hoje.
// Padrão herdado de scripts/check/check-provider-consistency.ts (gate .ts via
// `node --import tsx` que IMPORTA módulos reais + funções puras + main() guardado).
//
// Stale-enforcement (6A.3): a ÚNICA allowlist de SUPRESSÃO deste gate é
// IMPLICIT_DEFAULT_STRATEGIES — cada entrada suprime uma violação `canonicalNotHandled`
// (estratégia canônica sem branch de despacho). Uma entrada que não suprime mais
// nenhuma violação real (porque a estratégia ganhou um branch `strategy === "..."`)
// é obsoleta → o gate falha com instrução de remoção, fechando o furo de regressão
// silenciosa. As demais listas (KNOWN_TRANSLATOR_PAIRS, KNOWN_MCP_TOOL_NAMES) NÃO são
// allowlists de supressão e sim snapshots-catraca (falham na REMOÇÃO, não na presença):
// uma entrada nelas exige que o par/tool continue VIVO no registry — o oposto de
// supressão — então a semântica de stale-enforcement não se aplica a elas.
import { readFileSync, readdirSync } from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import { dirname, resolve as resolvePath, basename, extname } from "node:path";
import { assertNoStale } from "./lib/allowlist.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolvePath(HERE, "..", "..");
@@ -53,12 +65,18 @@ const REPO_ROOT = resolvePath(HERE, "..", "..");
/**
* Estratégias canônicas que NÃO têm um branch `strategy === "..."` na cadeia de
* despacho porque são o comportamento padrão (sem reordenamento explícito). Cada
* uma documentada. Remover daqui se um branch dedicado for adicionado.
* uma documentada. Adicionar aqui se uma estratégia canônica não tiver NENHUMA
* referência `strategy === "..."` em combo.ts (do contrário extractHandledStrategies
* já a considera tratada e a entrada vira obsoleta — stale-enforcement abaixo falha).
*
* Atualmente vazio: a entrada `priority` foi removida porque combo.ts passou a
* referenciar `strategy === "priority"` (pre-screen de latência em resolveComboTargets),
* o que torna `priority` já-tratada por extractHandledStrategies — a supressão não
* suprimia mais nenhuma violação `canonicalNotHandled` (era stale). Se o pre-screen
* for removido no futuro, `priority` reaparecerá como `canonicalNotHandled` e o gate
* pedirá para refiá-la no despacho OU redocumentá-la aqui.
*/
export const IMPLICIT_DEFAULT_STRATEGIES: Record<string, string> = {
priority:
'Default sem branch: combo.ts não tem `strategy === "priority"`; cai no ordenamento padrão de resolveComboTargets (ordem de prioridade declarada). É o fallback de normalizeRoutingStrategy.',
};
export const IMPLICIT_DEFAULT_STRATEGIES: Record<string, string> = {};
/** Extrai todas as strings literais de `strategy === "..."` da fonte do combo. */
export function extractHandledStrategies(comboSource: string): Set<string> {
@@ -458,6 +476,16 @@ async function main(): Promise<void> {
const canonical = strategiesMod.ROUTING_STRATEGY_VALUES as readonly string[];
const comboSource = readFileSync(resolvePath(REPO_ROOT, "open-sse/services/combo.ts"), "utf8");
const handled = extractHandledStrategies(comboSource);
// Stale-enforcement (6A.3): IMPLICIT_DEFAULT_STRATEGIES is a suppression allowlist —
// each entry exists ONLY to suppress a `canonicalNotHandled` violation (a canonical
// strategy with no `strategy === "..."` dispatch reference). The live violations it
// suppresses are the canonical strategies NOT already in `handled` (computed with an
// EMPTY implicit-defaults map). An entry whose key IS already in `handled` suppresses
// nothing → it is stale and the gate must fail asking for its removal.
const liveImplicitNeeded = diffComboStrategies(canonical, handled, {}).canonicalNotHandled;
assertNoStale(Object.keys(IMPLICIT_DEFAULT_STRATEGIES), liveImplicitNeeded, "known-symbols:combo");
const { canonicalNotHandled, handledNotCanonical } = diffComboStrategies(
canonical,
handled,
@@ -635,6 +663,9 @@ async function main(): Promise<void> {
console.error(`[known-symbols] ${failures.length} sub-checagem(ns) falharam:\n\n${failures.join("\n\n")}`);
process.exit(1);
}
// assertNoStale (combo) seta process.exitCode=1 sem lançar — não imprima o OK
// enganoso; a mensagem de stale já foi logada no stderr pelo helper.
if (process.exitCode === 1) return;
const newPairsNote = newPairs.length
? ` (${newPairs.length} par(es) novo(s) não-congelado(s): ${newPairs.join(", ")} — atualize KNOWN_TRANSLATOR_PAIRS se intencional)`

View File

@@ -20,6 +20,13 @@ import {
diffCloudAgents,
type ExecutorLike,
} from "../../scripts/check/check-known-symbols.ts";
import { reportStaleEntries } from "../../scripts/check/lib/allowlist.mjs";
import { ROUTING_STRATEGY_VALUES } from "../../src/shared/constants/routingStrategies.ts";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../..");
// ───────────────────────────────────────────────────────────────────────────
// (2) COMBO STRATEGIES — extractHandledStrategies + diffComboStrategies
@@ -174,9 +181,57 @@ test("findNewTranslatorPairs returns [] when live is a subset of frozen", () =>
// Allowlist / snapshot sanity (documented frozen sets stay well-formed)
// ───────────────────────────────────────────────────────────────────────────
test("IMPLICIT_DEFAULT_STRATEGIES documents `priority` with a justification", () => {
assert.ok(Object.prototype.hasOwnProperty.call(IMPLICIT_DEFAULT_STRATEGIES, "priority"));
assert.ok(IMPLICIT_DEFAULT_STRATEGIES.priority.length > 20);
test("IMPLICIT_DEFAULT_STRATEGIES: every documented key carries a non-trivial justification", () => {
// The map may be empty (all canonical strategies are referenced in combo.ts), but any
// entry that DOES exist must explain why the strategy has no explicit dispatch branch.
for (const [key, justification] of Object.entries(IMPLICIT_DEFAULT_STRATEGIES)) {
assert.ok(typeof justification === "string" && justification.length > 20, `weak/missing justification for "${key}"`);
}
});
// --- stale-allowlist enforcement (6A.3) ---
test("stale-enforcement: an IMPLICIT_DEFAULT_STRATEGIES key now referenced in dispatch is reported as stale", () => {
// "priority" gained a `strategy === "priority"` reference in combo.ts, so it is now in
// the handled set and no longer in canonicalNotHandled — an implicit-default for it would
// suppress nothing → stale.
const liveImplicitNeeded: string[] = []; // canonicalNotHandled WITHOUT the allowlist
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
["priority"],
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, ["priority"]);
});
test("stale-enforcement: an IMPLICIT_DEFAULT_STRATEGIES key still uncovered by dispatch is NOT stale", () => {
// A canonical strategy with no `strategy === "..."` reference IS still suppressed by the
// implicit-default, so the entry is live (must stay).
const liveImplicitNeeded = ["priority"]; // priority would be canonicalNotHandled without it
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
["priority"],
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, []);
});
test("stale-enforcement: live repo IMPLICIT_DEFAULT_STRATEGIES has no stale entries", () => {
// Every key must still be a canonical strategy that lacks a `strategy === "..."` reference
// in combo.ts; otherwise the suppression is dead and must be removed.
const comboSource = fs.readFileSync(path.join(REPO_ROOT, "open-sse/services/combo.ts"), "utf8");
const handled = extractHandledStrategies(comboSource);
const liveImplicitNeeded = diffComboStrategies(
ROUTING_STRATEGY_VALUES as readonly string[],
handled,
{}
).canonicalNotHandled;
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
Object.keys(IMPLICIT_DEFAULT_STRATEGIES),
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, [], `IMPLICIT_DEFAULT_STRATEGIES has stale entries: ${stale.join(", ")}`);
});
test("KNOWN_TRANSLATOR_PAIRS is a non-empty, well-formed, deduped from:to snapshot", () => {