Skip to content

feat(compression): opt-in per-step fidelity gate (+ playground toggle) - #5127

Merged
diegosouzapw merged 7 commits into
release/v3.8.38from
feat/compression-fidelity-gate
Jun 27, 2026
Merged

diegosouzapw merged 7 commits into
release/v3.8.38from
feat/compression-fidelity-gate

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

What

An opt-in, per-step deterministic fidelity gate for the stacked compression pipeline. After each lossy engine, checkFidelity runs 4 invariants; if the engine's output fails, its output is rejected (the step's input is kept) and the engineBreakdown entry is marked rejected + rejectReason. Default off → byte-identical to today. Second feature of the compression feature-extraction roadmap (validated on the playground bench, PR #5080).

Spec/plan: docs/superpowers/{specs,plans}/2026-06-26-compression-fidelity-gate* (gitignored tooling).

The gate

  • open-sse/services/compression/fidelityGate.ts — pure checkFidelity(inputText, outputText, cfg), 4 invariants (cheap→expensive, short-circuit):
    1. protected-tokens ≥95% survive — critical literals from preservation.ts (url / const_case / env_var / version / dotted_identifier / function_call / file_path / inline_code; markdown structure excluded to avoid false rejections on legitimate reformatting).
    2. diff-hunks — every @@ -a,b +c,d @@ header survives.
    3. numeric — every input number literal survives (catches 14 passed → 4 passed, ports, values).
    4. json-keys ≥90% survive (catches the packages → pkgs key-rename class).
    • fail-open (verifier bug never blocks compression); all regexes bounded (anti-ReDoS).
  • Wiring (strategySelector.ts): gateAdvance AND-ed into the 4 advance sites (sync/async × bailout/else). Off → return true on the first line (zero-cost, byte-identical). Independent of TV1. A no-stats step that fails the gate does not mark the prior engine's breakdown entry (if (result.stats) guard, regression-tested).
  • Config: CompressionConfig.fidelityGate? (opt-in) flows to the gate via options.config.fidelityGate.

Playground toggle

A "Verificar fidelidade" checkbox in the studio Play tab passes fidelityGate:{enabled:true} to /api/compression/preview; rejected lanes show ⚠ rejeitado: <invariant>. Verified functionally end-to-end (a preview with a corrupting engine + the flag returns rejected:true, rejectReason:"número \"8080\" ausente no output" — not an inert flag).

Tests

  • Node runner (tests/unit/compression/): fidelityGate (8), fidelityGateTypes (1), fidelityGateStacked (3, incl. ON-rejects / OFF-byte-identical / no-stats-hardening), previewRouteFidelity (2).
  • Vitest UI (tests/unit/ui/fidelityGateToggle.test.tsx, 1).
  • Full compression suite 940/940, full vitest 202/202 (no regression; the OFF path is byte-identical). typecheck:core / lint / check:cycles green. vitest.config.ts untouched.

CI note — the check:complexity red is pre-existing DRIFT, not this PR

check:complexity reports 1972 > baseline 1963. Verified the base commit d6f402e0a already reports 1972 (ran the gate on the base) — i.e. the frozen baseline (1963) is stale vs the current release/v3.8.38 state, and this branch adds zero new complexity violations (checkFidelity/gateAdvance are under the threshold; the flagged functions — applyCompression/applyUltraAsync/applyStackedCompression(Async) — are pre-existing). This is the usual release-branch ratchet drift (rebaselined at release), independent of this PR.

Known follow-ups (non-blocking)

  • The preview API surfaces only {enabled}; the advanced thresholds (minTokenSurvivalPercent/minJsonKeyPercent/checkNumericIntegrity/checkDiffHunks) use conservative defaults until the studio gets a config panel (documented intentional in the route).
  • compressionEventToModel (the WS live-feed path) doesn't propagate rejected/rejectReason — out of scope; for when the live dashboard gains gate support.
  • Bare relative paths without a leading ./ (e.g. src/lib/db/core.ts) aren't captured by preservation.ts so aren't in the protected-tokens set — consistent with what the engines protect; extending the path pattern is a system-wide change.

Ready for review & merge.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an opt-in per-step fidelity gate mechanism to prevent compression engines from corrupting critical data (such as protected tokens, numeric literals, JSON keys, and diff hunks). It integrates this gate into the stacked compression selector, updates the preview API and playground UI to support toggling the feature, and adds comprehensive unit and integration tests. Feedback was provided to add defensive checks in bodyToText to handle potentially malformed message elements and prevent runtime crashes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +653 to +657
function bodyToText(body: Record<string, unknown>): string {
const messages = body.messages;
if (!Array.isArray(messages)) return "";
return messages.map((m) => extractTextContent((m as { content?: unknown }).content as never)).join("\n");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes, add a defensive check to ensure each message m is a non-null object before attempting to access its content property. If messages contains malformed elements (e.g., null, undefined, or primitive values), accessing .content directly will throw a TypeError.

function bodyToText(body: Record<string, unknown>): string {
  const messages = body.messages;
  if (!Array.isArray(messages)) return "";
  return messages
    .map((m) => {
      if (!m || typeof m !== "object") return "";
      return extractTextContent((m as { content?: unknown }).content as never);
    })
    .join("\n");
}

…trategySelector (file-size gate)

bodyToText and gateAdvance moved to fidelityGateStep.ts; StackAccumulator exported.
strategySelector: 889->854 (-35). Residual +6 vs pre-Milestone-B frozen 848 is the
irreducible StackOptions.fidelityGate field + two stacked-loop dispatch reads + import.
Baseline updated to 854 with justification. No cycle introduced (import type only).
940 compression tests pass; typecheck clean.
@diegosouzapw

Copy link
Copy Markdown
Owner Author

Babysit summary — #5127

Status: ready for human review & merge (not auto-merged). I fixed the one red that was this PR's; the rest are pre-existing release-branch drift, proven below.

Fixed (this PR's own regression)

  • check:file-size on strategySelector.ts (4ef1c459f) — the Milestone-B per-step gate wiring grew the file 848→889. Fixed by extracting bodyToText + gateAdvance into a new open-sse/services/compression/fidelityGateStep.ts (import type StackAccumulator, no runtime cycle) → strategySelector 889→854. The residual +6 (the StackOptions.fidelityGate field, the two const fidelityGate reads at the loop chokepoints, the FidelityGateConfig import — irreducible loop wiring) is captured by a justified file-size-baseline.json bump (848→854). 940 compression tests + typecheck + cycles + eslint green locally.

Remaining CI red = PRE-EXISTING DRIFT on release/v3.8.38, NOT this PR

GitHub PR CI runs on the merge of this branch with the current release/v3.8.38 HEAD — which has advanced past this branch's base (d6f402e0a). Two ratchets drifted on the release independently of this PR:

  • check:file-size → providers.ts (1106 > 1093) + usageHistory.ts (982 > 934). Proven: on this branch these files are 1062 and 930 (under baseline); on origin/release/v3.8.38 they are 1106/982 (grown by parallel PRs). This PR's diff touches neither file.
  • check:complexity (1972 > 1963). Proven: the base commit d6f402e0a already reports 1972 (ran the gate on the base). This branch adds zero new complexity violations (checkFidelity/gateAdvance are under the threshold; the flagged functions are pre-existing).

Both are the usual release-branch ratchet drift, rebaselined at release time. Unit Tests fast-path (1/2) flaked on a pre-existing base-red this run (it + (2/2) both passed on the prior run); (2/2), Vitest, CodeQL, semgrep, DAST all green.

This PR's own surface — clean

  • 940 compression node tests + 202 vitest + typecheck:core + lint + check:cycles green. vitest.config.ts untouched. No AI footer in any commit.
  • The added fidelity gate is functionally wired end-to-end (verified: a preview with a corrupting engine + fidelityGate:{enabled:true} returns rejected:true, rejectReason:"número \"8080\" ausente no output" — not an inert flag). Default off → byte-identical to today (the full suite passes unchanged).

Ready for review & merge (the file-size/complexity release drift rebaselines at release).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant