mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 13:14:56 +03:00
feat(compression): contabilidade física do omniglyph com grau de evidência
O adapter descartava o TransformInfo inteiro, então a UI mostrava um número de economia sem dizer de onde ele vinha — contagem do provider, estimativa ou só diferença de bytes. O 1.4.0 expõe normalizeAccounting(), que classifica essa evidência e resolve a semântica de cache por família: Anthropic reporta input, cache-create e cache-read em buckets DISJUNTOS, enquanto OpenAI e xAI reportam cached como SUBCONJUNTO do input. Somar à mão dá double-count silencioso. O novo omniglyphTelemetry.ts não filtra por denylist — MONTA um objeto novo, campo a campo, só com número e enum. TransformInfo mistura contadores inofensivos com material que não pode ser persistido: bytes PNG, imageSourceText(s), recoverable[].text, os sha8 de system/CLAUDE.md/primeira mensagem, nomes de tags observadas e o bloco env (cwd, branch, versões). Copiar o objeto inteiro transformaria telemetria de compressão em vazamento de prompt. O teste de negação prova que segredo, caminho do operador, texto do system e base64 não aparecem, e varre a allowlist exigindo que toda string seja de um enum conhecido. - provider threaded do chatCore e do bridge Codex WS até a engine; ausente vira `unknown`, que faz o upstream recusar adivinhar buckets de cache; - contabilidade propagada para o engineBreakdown do passo (o agregado do pipeline soma todas as engines e não serviria); - skip não emite contabilidade: zeros ali seriam indistinguíveis de "a engine nem rodou".
This commit is contained in:
@@ -1552,6 +1552,10 @@ export async function handleChatCore({
|
||||
// OmniGlyph uses a measured provider/image-fidelity allowlist. Direct HTTP
|
||||
// alone is not proof that a route preserves PNG bytes and dimensions.
|
||||
...resolveOmniGlyphTransport(provider),
|
||||
// Sem o provider, a contabilidade do OmniGlyph cai para `unknown` e
|
||||
// recusa deduzir a semântica de cache (Anthropic usa buckets disjuntos,
|
||||
// OpenAI reporta cached como subconjunto do input).
|
||||
provider,
|
||||
sourceFormat,
|
||||
targetFormat,
|
||||
compressionStage: "pre-translation" as const,
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
import type { CompressionEngine, CompressionEngineApplyOptions } from "./types.ts";
|
||||
import type { CompressionResult } from "../types.ts";
|
||||
import { createCompressionStats } from "../stats.ts";
|
||||
import {
|
||||
buildOmniGlyphAccounting,
|
||||
type OmniGlyphAccounting,
|
||||
} from "../omniglyphTelemetry.ts";
|
||||
import {
|
||||
isOmniGlyphSupportedModelForScope,
|
||||
mergeCompressionProfileOptions,
|
||||
@@ -209,6 +213,7 @@ async function applyOmniglyph(
|
||||
if (!isModelImageable(model)) return skip(body, "model_not_imageable");
|
||||
const started = Date.now();
|
||||
let outBody: Record<string, unknown>;
|
||||
let accounting: OmniGlyphAccounting | undefined;
|
||||
try {
|
||||
// The upstream OpenAI transformer resolves its billing/render profile from
|
||||
// body.model. Keep the provider body byte-compatible on output, but use the
|
||||
@@ -235,24 +240,34 @@ async function applyOmniglyph(
|
||||
if (!applied) return skip(body, result.info?.reason ?? "not_profitable");
|
||||
outBody = JSON.parse(new TextDecoder().decode(result.body)) as Record<string, unknown>;
|
||||
if (transformBody !== body && body.model !== undefined) outBody.model = body.model;
|
||||
accounting = buildOmniGlyphAccounting({
|
||||
provider: options?.provider,
|
||||
model,
|
||||
originalBytes: encoded.byteLength,
|
||||
transformedBytes: result.body.byteLength,
|
||||
info: result.info,
|
||||
durationMs: Date.now() - started,
|
||||
});
|
||||
} catch {
|
||||
// Fail-open: qualquer erro no encode/transform/decode (ex.: corpo não serializável,
|
||||
// render PNG estourando, JSON decodificado malformado) vira skip, nunca propaga.
|
||||
return skip(body, "transform_error");
|
||||
}
|
||||
|
||||
return {
|
||||
body: outBody,
|
||||
compressed: true,
|
||||
stats: createCompressionStats(
|
||||
body,
|
||||
outBody,
|
||||
"stacked",
|
||||
["omniglyph:context-as-image"],
|
||||
undefined,
|
||||
Date.now() - started
|
||||
),
|
||||
};
|
||||
const stats = createCompressionStats(
|
||||
body,
|
||||
outBody,
|
||||
"stacked",
|
||||
["omniglyph:context-as-image"],
|
||||
undefined,
|
||||
Date.now() - started
|
||||
);
|
||||
// A contabilidade só acompanha uma conversão que realmente aconteceu: um skip
|
||||
// não tem economia para reportar, e inventar zeros ali viraria "0% de ganho"
|
||||
// indistinguível de "a engine nem rodou".
|
||||
if (accounting) stats.omniglyph = accounting;
|
||||
|
||||
return { body: outBody, compressed: true, stats };
|
||||
}
|
||||
|
||||
export const omniglyphEngine: CompressionEngine = {
|
||||
|
||||
@@ -61,6 +61,10 @@ export interface CompressionEngineApplyOptions {
|
||||
stepConfig?: Record<string, unknown>;
|
||||
/** Authenticated principal (API key id) making the request. Used by CCR to scope its store. */
|
||||
principalId?: string;
|
||||
/** Provider resolvido do alvo. A contabilidade do omniglyph depende dele:
|
||||
* Anthropic reporta input/cache em buckets disjuntos, OpenAI/xAI reportam
|
||||
* cached como subconjunto do input. Ausente => `unknown` (falha fechado). */
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
export interface CompressionEngine {
|
||||
|
||||
156
open-sse/services/compression/omniglyphTelemetry.ts
Normal file
156
open-sse/services/compression/omniglyphTelemetry.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Ponte de telemetria do OmniGlyph — allowlist positiva.
|
||||
*
|
||||
* `TransformInfo` mistura contadores inofensivos com material que NUNCA pode
|
||||
* ser persistido: bytes PNG, `imageSourceText(s)`, `recoverable[].text`, os
|
||||
* sha8 de system/CLAUDE.md/primeira mensagem, os nomes de tags observadas e o
|
||||
* bloco `env` (cwd, branch, versões). Copiar o objeto inteiro seria transformar
|
||||
* a telemetria de compressão num vazamento do prompt.
|
||||
*
|
||||
* Este módulo não filtra por denylist — ele MONTA um objeto novo, campo a
|
||||
* campo, só com número e enum. Um campo novo no upstream não entra sozinho.
|
||||
*
|
||||
* `normalizeAccounting()` (OmniGlyph 1.4.0) faz a parte difícil: classifica o
|
||||
* grau de evidência da economia e resolve a semântica de cache por provider —
|
||||
* Anthropic reporta input/cache-create/cache-read em buckets DISJUNTOS,
|
||||
* enquanto OpenAI e xAI reportam `cached` como SUBCONJUNTO do input. Somar à
|
||||
* mão dá double-count silencioso.
|
||||
*/
|
||||
|
||||
import {
|
||||
normalizeAccounting,
|
||||
type AccountingProvider,
|
||||
type OmniGlyphTransformInfo,
|
||||
type SavingsEvidence,
|
||||
} from "omniglyph";
|
||||
|
||||
/** Contabilidade segura de uma execução do OmniGlyph. Só número e enum. */
|
||||
export interface OmniGlyphAccounting {
|
||||
provider: AccountingProvider;
|
||||
model?: string;
|
||||
bytes: {
|
||||
original?: number;
|
||||
transformed?: number;
|
||||
reduced?: number;
|
||||
compressionRatio?: number;
|
||||
};
|
||||
tokens: {
|
||||
estimatedOriginalInput?: number;
|
||||
estimatedActualInput?: number;
|
||||
estimatedReduced?: number;
|
||||
image?: number;
|
||||
};
|
||||
savings: {
|
||||
/** De onde saiu o número: contagem do provider, estimativa ou só bytes. */
|
||||
evidence: SavingsEvidence;
|
||||
inputTokensReduced?: number;
|
||||
inputReductionRatio?: number;
|
||||
};
|
||||
images: {
|
||||
count: number;
|
||||
bytes: number;
|
||||
pixels?: number;
|
||||
};
|
||||
/** Chars de origem imageados vs. mantidos como texto por turno. */
|
||||
chars: {
|
||||
original?: number;
|
||||
imaged?: number;
|
||||
static?: number;
|
||||
dynamic?: number;
|
||||
outgoingText?: number;
|
||||
};
|
||||
dynamicBlockCount?: number;
|
||||
latencyMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A semântica de cache de `normalizeAccounting` depende da família do provider,
|
||||
* não do nome comercial da rota. Rota desconhecida vira `unknown`, que faz o
|
||||
* upstream falhar fechado em vez de adivinhar buckets de cache.
|
||||
*/
|
||||
export function toAccountingProvider(provider: string | null | undefined): AccountingProvider {
|
||||
const normalized = typeof provider === "string" ? provider.trim().toLowerCase() : "";
|
||||
if (normalized === "anthropic" || normalized === "claude") return "anthropic";
|
||||
if (normalized === "openai" || normalized === "codex" || normalized === "chatgpt") {
|
||||
return "openai";
|
||||
}
|
||||
if (normalized === "xai" || normalized === "grok") return "xai";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function count(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export function buildOmniGlyphAccounting(params: {
|
||||
provider: string | null | undefined;
|
||||
model?: string;
|
||||
originalBytes: number;
|
||||
transformedBytes: number;
|
||||
info?: OmniGlyphTransformInfo | null;
|
||||
durationMs?: number;
|
||||
}): OmniGlyphAccounting {
|
||||
const { info } = params;
|
||||
const provider = toAccountingProvider(params.provider);
|
||||
|
||||
// `baselineImagedTokens` é o custo em tokens de texto do que foi imageado (o
|
||||
// "teria pago assim"); `imageTokens` é o que as imagens custam de fato. Os
|
||||
// dois só existem no wire GPT — no Anthropic a evidência honesta cai para
|
||||
// bytes, e é isso que o campo `evidence` passa a dizer em vez de exibir um
|
||||
// número sem procedência.
|
||||
const normalized = normalizeAccounting({
|
||||
provider,
|
||||
...(params.model ? { model: params.model } : {}),
|
||||
originalBytes: params.originalBytes,
|
||||
transformedBytes: params.transformedBytes,
|
||||
...(count(info?.baselineImagedTokens) !== undefined
|
||||
? { estimatedOriginalInputTokens: info!.baselineImagedTokens }
|
||||
: {}),
|
||||
...(count(info?.imageTokens) !== undefined
|
||||
? { estimatedTransformedInputTokens: info!.imageTokens }
|
||||
: {}),
|
||||
...(count(info?.imageTokens) !== undefined ? { imageTokens: info!.imageTokens } : {}),
|
||||
...(params.durationMs !== undefined ? { proxyAddedLatencyMs: params.durationMs } : {}),
|
||||
});
|
||||
|
||||
const chars = {
|
||||
...(count(info?.origChars) !== undefined ? { original: info!.origChars } : {}),
|
||||
...(count(info?.compressedChars) !== undefined ? { imaged: info!.compressedChars } : {}),
|
||||
...(count(info?.staticChars) !== undefined ? { static: info!.staticChars } : {}),
|
||||
...(count(info?.dynamicChars) !== undefined ? { dynamic: info!.dynamicChars } : {}),
|
||||
...(count(info?.outgoingTextChars) !== undefined
|
||||
? { outgoingText: info!.outgoingTextChars }
|
||||
: {}),
|
||||
};
|
||||
|
||||
return {
|
||||
provider: normalized.provider,
|
||||
...(normalized.model ? { model: normalized.model } : {}),
|
||||
bytes: normalized.bytes,
|
||||
tokens: {
|
||||
...(normalized.tokens.estimatedOriginalInput !== undefined
|
||||
? { estimatedOriginalInput: normalized.tokens.estimatedOriginalInput }
|
||||
: {}),
|
||||
...(normalized.tokens.estimatedActualInput !== undefined
|
||||
? { estimatedActualInput: normalized.tokens.estimatedActualInput }
|
||||
: {}),
|
||||
...(normalized.tokens.estimatedReduced !== undefined
|
||||
? { estimatedReduced: normalized.tokens.estimatedReduced }
|
||||
: {}),
|
||||
...(normalized.tokens.image !== undefined ? { image: normalized.tokens.image } : {}),
|
||||
},
|
||||
savings: normalized.savings,
|
||||
images: {
|
||||
count: count(info?.imageCount) ?? 0,
|
||||
bytes: count(info?.imageBytes) ?? 0,
|
||||
...(count(info?.imagePixels) !== undefined ? { pixels: info!.imagePixels } : {}),
|
||||
},
|
||||
chars,
|
||||
...(count(info?.dynamicBlockCount) !== undefined
|
||||
? { dynamicBlockCount: info!.dynamicBlockCount }
|
||||
: {}),
|
||||
...(normalized.latency.proxyAddedMs !== undefined
|
||||
? { latencyMs: normalized.latency.proxyAddedMs }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
@@ -110,5 +110,8 @@ export function mergeStackStep(
|
||||
techniquesUsed: result.stats.techniquesUsed,
|
||||
...(result.stats.rulesApplied ? { rulesApplied: result.stats.rulesApplied } : {}),
|
||||
...(result.stats.durationMs !== undefined ? { durationMs: result.stats.durationMs } : {}),
|
||||
// O agregado do pipeline soma tokens de todas as engines; a contabilidade
|
||||
// física do omniglyph só faz sentido no passo que a produziu.
|
||||
...(result.stats.omniglyph ? { omniglyph: result.stats.omniglyph } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,6 +482,8 @@ export async function applyCompressionAsync(
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
@@ -505,6 +507,8 @@ async function runCompressionAsync(
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
@@ -698,6 +702,8 @@ interface StackOptions {
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { RiskGateConfig } from "./riskGate/riskGate.ts";
|
||||
import type { PipelineCircuitBreakerConfig } from "./pipelineEngineBreaker.ts";
|
||||
import type { RiskGateStats } from "./riskGate/riskGateStep.ts";
|
||||
import type { QuantumLockConfig, QuantumLockStats } from "./quantumLock/quantumPatterns.ts";
|
||||
import type { OmniGlyphAccounting } from "./omniglyphTelemetry.ts";
|
||||
|
||||
// Re-export so consumers that already import from this module (e.g. src/lib/db/compression.ts)
|
||||
// can get ENGINE_IDS without a second bare `@omniroute/open-sse/...engineCatalog.ts` specifier.
|
||||
@@ -303,6 +304,12 @@ export interface CompressionStats {
|
||||
validationWarnings?: string[];
|
||||
validationErrors?: string[];
|
||||
fallbackApplied?: boolean;
|
||||
/**
|
||||
* Contabilidade física do OmniGlyph, normalizada pelo próprio pacote
|
||||
* (`normalizeAccounting`). Só número e enum — ver `omniglyphTelemetry.ts`
|
||||
* para a allowlist e o que nunca pode entrar aqui.
|
||||
*/
|
||||
omniglyph?: OmniGlyphAccounting;
|
||||
riskGate?: RiskGateStats;
|
||||
/**
|
||||
* Phase 4 (B): which `ultra` tier actually ran for this request.
|
||||
@@ -341,6 +348,8 @@ export interface CompressionStats {
|
||||
durationMs?: number;
|
||||
rejected?: boolean;
|
||||
rejectReason?: string;
|
||||
/** Contabilidade física — presente só no passo omniglyph que comprimiu. */
|
||||
omniglyph?: OmniGlyphAccounting;
|
||||
}>;
|
||||
/** Present only when QuantumLock stabilized ≥1 fragment this run. */
|
||||
quantumLock?: QuantumLockStats;
|
||||
|
||||
@@ -88,6 +88,7 @@ export async function applyResponsesWsCompression(
|
||||
const result = await applyCompressionAsync(adapter.body, mode, {
|
||||
model: ctx.model,
|
||||
...resolveOmniGlyphTransport(ctx.provider),
|
||||
provider: ctx.provider,
|
||||
config: settings as CompressionConfig,
|
||||
cachingContext,
|
||||
});
|
||||
|
||||
@@ -168,3 +168,28 @@ test("Claude→OpenAI aguarda o wire alvo e não imageia o corpo fonte", async (
|
||||
assert.equal(post.compressed, true);
|
||||
assert.ok(JSON.stringify(post.body).includes('"type":"image_url"'));
|
||||
});
|
||||
|
||||
// O `provider` é threaded explicitamente por cada camada do selector (não é um
|
||||
// spread cego), então uma camada nova pode derrubá-lo em silêncio: a
|
||||
// contabilidade cairia para `unknown` e passaria a recusar a semântica de cache
|
||||
// sem nenhum erro aparecer.
|
||||
test("provider chega do selector até a contabilidade da engine", async () => {
|
||||
registerBuiltinCompressionEngines();
|
||||
const r = await applyCompressionAsync(body(), "stacked", {
|
||||
model: "claude-fable-5",
|
||||
provider: "anthropic",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct",
|
||||
imageTransportFidelity: "byte-preserving",
|
||||
config: { stackedPipeline: [{ engine: "omniglyph" }] } as never,
|
||||
});
|
||||
assert.equal(r.compressed, true);
|
||||
const omniglyphStep = r.stats?.engineBreakdown?.find((e) => e.engine === "omniglyph");
|
||||
assert.ok(omniglyphStep, "omniglyph step deveria aparecer no engineBreakdown");
|
||||
assert.equal(
|
||||
omniglyphStep!.omniglyph?.provider,
|
||||
"anthropic",
|
||||
"sem o provider a contabilidade cai para unknown e recusa a semântica de cache"
|
||||
);
|
||||
assert.equal(omniglyphStep!.omniglyph?.savings.evidence, "bytes-only");
|
||||
});
|
||||
|
||||
121
tests/unit/compression/omniglyph-telemetry.test.ts
Normal file
121
tests/unit/compression/omniglyph-telemetry.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { omniglyphEngine } from "../../../open-sse/services/compression/engines/omniglyphAdapter.ts";
|
||||
|
||||
// O OmniGlyph 1.4.0 expõe `normalizeAccounting()`, que classifica o GRAU DE
|
||||
// EVIDÊNCIA da economia (provider-reported / estimated / bytes-only /
|
||||
// unavailable) e resolve a semântica de cache por provider — Anthropic usa
|
||||
// buckets disjuntos, OpenAI/xAI reportam cached como subconjunto do input.
|
||||
// Somar à mão dá double-count. Antes disso o adapter descartava tudo e a UI
|
||||
// exibia um número sem dizer de onde ele veio.
|
||||
|
||||
const SEGREDO = "sk-ant-api03-SEGREDO-QUE-NAO-PODE-VAZAR-NA-TELEMETRIA";
|
||||
const DENSE =
|
||||
"X".repeat(500) +
|
||||
"\n" +
|
||||
Array.from(
|
||||
{ length: 400 },
|
||||
(_, i) => `const row_${i} = compute(${i * 17}, "${"v".repeat(80)}");`
|
||||
).join("\n");
|
||||
|
||||
function claudeBody(): Record<string, unknown> {
|
||||
return {
|
||||
model: "claude-fable-5",
|
||||
max_tokens: 128,
|
||||
system: `${DENSE}\nAPI_KEY=${SEGREDO}\ncwd=/home/operador/projeto-secreto`,
|
||||
messages: [{ role: "user", content: [{ type: "text", text: "oi" }] }],
|
||||
};
|
||||
}
|
||||
|
||||
const OK = {
|
||||
model: "claude-fable-5",
|
||||
provider: "anthropic",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct" as const,
|
||||
imageTransportFidelity: "byte-preserving" as const,
|
||||
};
|
||||
|
||||
test("stats do omniglyph carregam a contabilidade normalizada com grau de evidência", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), OK as never);
|
||||
assert.equal(r.compressed, true);
|
||||
|
||||
const accounting = r.stats?.omniglyph;
|
||||
assert.ok(accounting, "compressão aplicada deve registrar a contabilidade");
|
||||
assert.equal(accounting.provider, "anthropic");
|
||||
assert.ok(
|
||||
["provider-reported", "estimated", "bytes-only", "unavailable"].includes(
|
||||
accounting.savings.evidence
|
||||
),
|
||||
`evidência inesperada: ${accounting.savings.evidence}`
|
||||
);
|
||||
// Sem contadores do provider nesta etapa, a base honesta é o tamanho do corpo.
|
||||
assert.equal(accounting.savings.evidence, "bytes-only");
|
||||
assert.ok(typeof accounting.bytes.original === "number" && accounting.bytes.original > 0);
|
||||
assert.ok(typeof accounting.bytes.transformed === "number");
|
||||
assert.ok(typeof accounting.images.count === "number" && accounting.images.count > 0);
|
||||
assert.ok(typeof accounting.images.bytes === "number" && accounting.images.bytes > 0);
|
||||
});
|
||||
|
||||
test("telemetria não carrega conteúdo: nem base64, nem prompt, nem segredo, nem ambiente", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), OK as never);
|
||||
assert.equal(r.compressed, true);
|
||||
|
||||
const serialized = JSON.stringify(r.stats);
|
||||
|
||||
// Valores do request.
|
||||
assert.ok(!serialized.includes(SEGREDO), "segredo do system vazou na telemetria");
|
||||
assert.ok(!serialized.includes("projeto-secreto"), "caminho do operador vazou");
|
||||
assert.ok(!serialized.includes("const row_0 = compute"), "texto do system vazou");
|
||||
assert.ok(!serialized.includes("data:image/png;base64"), "data URL de imagem vazou");
|
||||
assert.ok(!serialized.includes("iVBORw0KGgo"), "cabeçalho PNG em base64 vazou");
|
||||
|
||||
// Chaves do TransformInfo que carregam conteúdo, hash de conteúdo ou ambiente.
|
||||
for (const proibida of [
|
||||
"imageSourceText",
|
||||
"imageSourceTexts",
|
||||
"recoverable",
|
||||
"systemSha8",
|
||||
"claudeMdSha8",
|
||||
"firstUserSha8",
|
||||
"unknownStaticTags",
|
||||
"churningStaticTags",
|
||||
'"env"',
|
||||
]) {
|
||||
assert.ok(!serialized.includes(proibida), `campo proibido na telemetria: ${proibida}`);
|
||||
}
|
||||
|
||||
// A allowlist é positiva: todo valor da contabilidade é número, string de enum
|
||||
// conhecida, ou objeto desses. Nada de texto livre vindo do request.
|
||||
const accounting = r.stats?.omniglyph;
|
||||
assert.ok(accounting);
|
||||
const enums = new Set([
|
||||
"anthropic",
|
||||
"openai",
|
||||
"xai",
|
||||
"unknown",
|
||||
"provider-reported",
|
||||
"estimated",
|
||||
"bytes-only",
|
||||
"unavailable",
|
||||
"claude-fable-5",
|
||||
]);
|
||||
const walk = (value: unknown, path: string): void => {
|
||||
if (value === undefined || value === null || typeof value === "number") return;
|
||||
if (typeof value === "string") {
|
||||
assert.ok(enums.has(value), `string fora da allowlist em ${path}: ${value}`);
|
||||
return;
|
||||
}
|
||||
assert.equal(typeof value, "object", `tipo inesperado em ${path}`);
|
||||
for (const [k, v] of Object.entries(value as Record<string, unknown>)) walk(v, `${path}.${k}`);
|
||||
};
|
||||
walk(accounting, "omniglyph");
|
||||
});
|
||||
|
||||
test("skip não inventa contabilidade", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), {
|
||||
...OK,
|
||||
imageTransportFidelity: "unknown",
|
||||
} as never);
|
||||
assert.equal(r.compressed, false);
|
||||
assert.equal(r.stats?.omniglyph, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user