mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Release v3.8.46
Release v3.8.46. Full changelog: CHANGELOG.md → [3.8.46]. Contributor attribution in the CHANGELOG entries.
This commit is contained in:
committed by
GitHub
parent
3ddcee6369
commit
92715c8f2c
@@ -61,12 +61,6 @@ export const KNOWN_STALE_DOC_REFS = new Set([
|
||||
// (DISCOVERY_TOOL_DESIGN.md saiu de docs/research/ para o repo isolado _tasks/research/
|
||||
// — gitignored, fora do escopo deste gate. As 4 entradas /api/discovery/* viraram
|
||||
// obsoletas e foram removidas para satisfazer o stale-enforcement da allowlist.)
|
||||
// docs/reference/ENVIRONMENT.md — endpoint UPSTREAM do provedor Blackbox Web,
|
||||
// citado na descrição de env var (não é rota do OmniRoute):
|
||||
"/api/chat",
|
||||
// docs/ops/TUNNELS_GUIDE.md — a doc afirma EXPLICITAMENTE que este endpoint NÃO
|
||||
// existe ("There is no central /api/settings/tunnels endpoint"); menção pedagógica:
|
||||
"/api/settings/tunnels",
|
||||
]);
|
||||
|
||||
function walk(dir, filter, acc = []) {
|
||||
|
||||
@@ -67,6 +67,11 @@ export const COLLECTORS = [
|
||||
// 24.18 do CI; no 24.16 local vira um crawl de ~60s/arquivo). O resto da suíte roda
|
||||
// sob tsx/esm (~-50% de bootstrap por processo). Plano mestre testes+CI, QW-b.
|
||||
{ glob: "tests/unit/dashboard/**/*.test.ts", sources: ["package.json"] },
|
||||
// Quarentena de flakes de concorrência (plano melhorias v3.8.46, P0.3): arquivos
|
||||
// sensíveis a contenção de CPU/timing (classe glm-3580 / quota-division /
|
||||
// provider-health-autopilot) rodam num passo dedicado --test-concurrency=1 ao FIM
|
||||
// de cada runner. Fora dos globs paralelos acima por diretório próprio.
|
||||
{ glob: "tests/unit/serial/**/*.test.ts", sources: ["package.json"] },
|
||||
// Órfãos religados (plano mestre QW-c): arquivos .test.mjs (top-level + db/ + feature-triage/) — fora do glob
|
||||
// *.test.ts histórico, nunca rodava em job nenhum (53 casos recuperados).
|
||||
{ glob: "tests/unit/**/*.test.mjs", sources: ["package.json"] },
|
||||
|
||||
@@ -15,6 +15,8 @@ import { execFileSync } from "node:child_process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const TEST_RE = /\.(test|spec)\.(ts|tsx)$/;
|
||||
// Production TypeScript sources (excludes test files, handled separately via TEST_RE).
|
||||
const PROD_SRC_RE = /\.(ts|tsx|mts|cts)$/;
|
||||
|
||||
/** Conta chamadas de assert.*( / assert( / expect( . */
|
||||
export function countAssertions(src) {
|
||||
@@ -57,6 +59,169 @@ export function countExtendedTautologies(src) {
|
||||
return count;
|
||||
}
|
||||
|
||||
// ─── (6348) Subcheck 4: inline-reimplemented prod conditions (REPORT-ONLY) ───
|
||||
// A test that copies a conditional expression out of production code (instead of
|
||||
// importing and exercising the symbol that owns it) is the wrong-shape-contract-test
|
||||
// class (#6216): the assertion re-encodes the branch locally, so it stays green even
|
||||
// when the real prod condition drifts. This subcheck is a HEURISTIC, textual gate
|
||||
// mirroring the count* helpers above — it never parses an AST. It is REPORT-ONLY for
|
||||
// now (warns, does not fail the gate).
|
||||
|
||||
/** Collapse all runs of whitespace to a single space and trim. */
|
||||
function normalizeWhitespace(s) {
|
||||
return (s || "").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count "significant" tokens in a normalized condition. Single-char identifiers
|
||||
* (`x`, `i`) and single-digit numeric literals (`0`, `1`) are treated as noise and
|
||||
* NOT counted; operators and multi-char identifiers/numbers ARE. This is what makes
|
||||
* `x > 0` trivial (1 significant token) while `status >= 500` is meaningful (3):
|
||||
* - `status >= 500` → status, >=, 500 → 3
|
||||
* - `x === LIMIT && y` → ===, LIMIT, && → 3
|
||||
* - `x > 0` → > → 1
|
||||
*/
|
||||
export function countSignificantTokens(cond) {
|
||||
const tokens =
|
||||
(cond || "").match(
|
||||
/===|!==|==|!=|>=|<=|&&|\|\||[<>+\-*/%!]|[A-Za-z_$][\w$]*|\d+(?:\.\d+)?/g
|
||||
) || [];
|
||||
let count = 0;
|
||||
for (const tk of tokens) {
|
||||
if (/^[A-Za-z_$]/.test(tk)) {
|
||||
if (tk.length >= 2) count++; // multi-char identifier
|
||||
} else if (/^\d/.test(tk)) {
|
||||
if (tk.length >= 2) count++; // multi-digit number
|
||||
} else {
|
||||
count++; // operator
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** A condition is "meaningful" when it carries ≥3 significant tokens. */
|
||||
function isSignificantCondition(cond) {
|
||||
return countSignificantTokens(cond) >= 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract meaningful (≥3-token) conditional expressions from a production source,
|
||||
* paired with the nearest enclosing declared symbol that "owns" them. Covers
|
||||
* `if (...)` (via paren balancing) and comparison-bearing ternaries (`a === b ? … : …`).
|
||||
* Returns [{ condition (whitespace-normalized), owner }].
|
||||
*/
|
||||
export function extractProdConditions(src) {
|
||||
const results = [];
|
||||
if (!src) return results;
|
||||
|
||||
// Declarations (function / const / let / var) with their positions, so each
|
||||
// condition can be attributed to the symbol whose body it lives in.
|
||||
const decls = [];
|
||||
const declRe =
|
||||
/(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)|(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g;
|
||||
let dm;
|
||||
while ((dm = declRe.exec(src))) {
|
||||
decls.push({ index: dm.index, name: dm[1] || dm[2] });
|
||||
}
|
||||
const ownerAt = (idx) => {
|
||||
let owner = "";
|
||||
for (const d of decls) {
|
||||
if (d.index <= idx) owner = d.name;
|
||||
else break;
|
||||
}
|
||||
return owner;
|
||||
};
|
||||
|
||||
const seen = new Set();
|
||||
const pushCond = (raw, owner) => {
|
||||
const norm = normalizeWhitespace(raw);
|
||||
if (!norm || seen.has(norm) || !isSignificantCondition(norm)) return;
|
||||
seen.add(norm);
|
||||
results.push({ condition: norm, owner });
|
||||
};
|
||||
|
||||
// if (...) — balance parentheses to capture the full condition.
|
||||
const ifRe = /\bif\s*\(/g;
|
||||
let m;
|
||||
while ((m = ifRe.exec(src))) {
|
||||
let depth = 1;
|
||||
let i = m.index + m[0].length;
|
||||
for (; i < src.length && depth > 0; i++) {
|
||||
const ch = src[i];
|
||||
if (ch === "(") depth++;
|
||||
else if (ch === ")") depth--;
|
||||
}
|
||||
pushCond(src.slice(m.index + m[0].length, i - 1), ownerAt(m.index));
|
||||
}
|
||||
|
||||
// Comparison-bearing ternaries: `<lhs> <cmp> <rhs> ? … : …` (best-effort, low-noise).
|
||||
const ternRe =
|
||||
/([A-Za-z_$][\w$).\]]*\s*(?:===|!==|==|!=|>=|<=|>|<)\s*[^?;{}\n]+?)\s*\?/g;
|
||||
let t;
|
||||
while ((t = ternRe.exec(src))) {
|
||||
pushCond(t[1], ownerAt(t.index));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the identifiers/module specifiers a test file imports, so we can tell
|
||||
* whether it exercises a prod symbol through the real import (clean) or merely
|
||||
* re-implements one of its conditions locally (masked). Returns a Set of names:
|
||||
* imported bindings, module paths, and module basenames.
|
||||
*/
|
||||
export function extractImports(src) {
|
||||
const names = new Set();
|
||||
if (!src) return names;
|
||||
const addModule = (mod) => {
|
||||
names.add(mod);
|
||||
const base = mod.split("/").pop().replace(/\.\w+$/, "");
|
||||
if (base) names.add(base);
|
||||
};
|
||||
let m;
|
||||
const importRe = /import\s+(?:type\s+)?([^;]*?)\s+from\s+['"]([^'"]+)['"]/g;
|
||||
while ((m = importRe.exec(src))) {
|
||||
addModule(m[2]);
|
||||
for (const id of m[1].match(/[A-Za-z_$][\w$]*/g) || []) {
|
||||
if (id !== "as" && id !== "type") names.add(id);
|
||||
}
|
||||
}
|
||||
const dynRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
||||
while ((m = dynRe.exec(src))) addModule(m[1]);
|
||||
const reqRe = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
||||
while ((m = reqRe.exec(src))) addModule(m[1]);
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* PURE core of subcheck 4. Given the sources of the prod files changed in a PR,
|
||||
* one test file's source, and the set of names that test imports: return the prod
|
||||
* conditions the test re-implements textually WITHOUT importing the symbol that owns
|
||||
* them. Whitespace is squashed on both sides so spacing differences never mask a hit.
|
||||
* Returns [{ condition, owner }].
|
||||
*/
|
||||
export function findReimplementedConditions(prodSources, testSource, testImports) {
|
||||
const flags = [];
|
||||
if (!testSource) return flags;
|
||||
const imports =
|
||||
testImports instanceof Set ? testImports : new Set(testImports || []);
|
||||
const squash = (s) => (s || "").replace(/\s+/g, "");
|
||||
const testSq = squash(testSource);
|
||||
const seen = new Set();
|
||||
for (const prod of prodSources || []) {
|
||||
for (const { condition, owner } of extractProdConditions(prod)) {
|
||||
if (owner && imports.has(owner)) continue; // exercised through the real import
|
||||
if (seen.has(condition)) continue;
|
||||
if (testSq.includes(squash(condition))) {
|
||||
seen.add(condition);
|
||||
flags.push({ condition, owner: owner || null });
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* (6A.10 subcheck 1) Sinaliza arquivos de teste DELETADOS ou renomeados-e-não-
|
||||
* substituídos. Recebe lista de paths de arquivos de teste que foram deletados
|
||||
@@ -241,14 +406,59 @@ function main() {
|
||||
// Only exempts the reduction signal; tautology/skip/deletion signals still fire.
|
||||
let assertReductionAllowlist = new Set();
|
||||
let deletionAllowlist = {};
|
||||
let reimplementedAllowlist = new Set();
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync("config/quality/test-masking-allowlist.json", "utf8"));
|
||||
assertReductionAllowlist = new Set(Object.keys(raw).filter((k) => !k.startsWith("_")));
|
||||
deletionAllowlist = raw._deletedWithReplacement || {};
|
||||
reimplementedAllowlist = new Set(raw._reimplementedConditions || []);
|
||||
} catch {
|
||||
// no allowlist file — treat as empty
|
||||
}
|
||||
|
||||
// (6348 subcheck 4, REPORT-ONLY) Tests that inline-reimplement a prod condition
|
||||
// instead of importing the symbol that owns it. Prod files changed in this PR
|
||||
// (added/copied/modified TS sources) are the reference corpus; each changed test
|
||||
// file is scanned against them. Warns only — it never fails the gate for now.
|
||||
const prodChanged = git(["diff", "--name-only", "--diff-filter=ACM", `${base}...HEAD`])
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter((f) => PROD_SRC_RE.test(f) && !TEST_RE.test(f) && fs.existsSync(f));
|
||||
const prodSources = prodChanged.map((f) => {
|
||||
try {
|
||||
return fs.readFileSync(f, "utf8");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
const changedTests = git(["diff", "--name-only", "--diff-filter=ACM", `${base}...HEAD`])
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter((f) => TEST_RE.test(f) && fs.existsSync(f));
|
||||
const reimplementedFlags = [];
|
||||
if (prodSources.length) {
|
||||
for (const tf of changedTests) {
|
||||
if (reimplementedAllowlist.has(tf)) continue;
|
||||
const src = fs.readFileSync(tf, "utf8");
|
||||
for (const hit of findReimplementedConditions(prodSources, src, extractImports(src))) {
|
||||
reimplementedFlags.push(
|
||||
`${tf}: re-implementa a condição \`${hit.condition}\`` +
|
||||
(hit.owner ? ` (dona: ${hit.owner})` : "") +
|
||||
" — asserte através do import real em vez de copiar a condição"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reimplementedFlags.length) {
|
||||
console.warn(
|
||||
`[test-masking] (report-only) ${reimplementedFlags.length} teste(s) re-implementam ` +
|
||||
`condição de produção em vez de importar o símbolo dono (classe #6216):\n` +
|
||||
reimplementedFlags.map((f) => " ⚠ " + f).join("\n") +
|
||||
`\n → importe o símbolo/função dono e asserte através dele (evita contrato duplicado ` +
|
||||
`que diverge silenciosamente). Report-only por enquanto — não falha o gate.`
|
||||
);
|
||||
}
|
||||
|
||||
const deletedFlags = evaluateDeletedFiles(
|
||||
[...deletedTests, ...relocatedOutOfTest],
|
||||
deletionAllowlist
|
||||
|
||||
Reference in New Issue
Block a user