Skip to content

fix(gateway): forward the caller's bytes, and measure them - #23

Closed
ojassug wants to merge 3 commits into
mainfrom
feat-m7-wire-metrics
Closed

fix(gateway): forward the caller's bytes, and measure them#23
ojassug wants to merge 3 commits into
mainfrom
feat-m7-wire-metrics

Conversation

@ojassug

@ojassug ojassug commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Closes audit M7 — the last open finding in max_audit.md.

M7 said Gateway savings are computed from summary.tokenEstimate, a property of the bundle render, while what leaves the process is JSON.stringify({...parsedPayload, messages}). It listed three consequences. All three were live when measured, and — as docs/audit-remediation-status.md §6 warned — they are not the same defect. The re-serialization half is the one that mattered.

What re-serialization was doing

Measured on a hand-written payload with one elision firing, which is the only shape the Gateway saves anything on:

client sent provider received
"temperature": 1.0 "temperature":1
"top_p": 1e3 "top_p":1000
"seed": 12345678901234567890 "seed":12345678901234567000

The first two are cosmetic. The third is a different number. An integer past 2^53 does not survive JSON.parseJSON.stringify, so a provider was being asked for a seed the caller never chose — by a proxy whose entire promise is faithfulness. Duplicate keys collapsed the same way, and pretty-printing was lost.

This is the mechanism the project already identified as the phantom -1.39% in the Python benchmark harness (Issue 5), reproduced in production code. It was found there by measuring, and found here the same way.

The fix is a splice, not a smaller re-serialization

Elided content is written into the caller's own bytes: each message's content is located by the canonical JSON encoding of the text the parser produced, searched forward from the previous message's end, and only that span is replaced. Every other byte is the caller's.

The forward cursor is the design, not an optimization. A first version searched globally and refused ambiguous matches — which declined every payload the Gateway can save on, because cleanup:session-dedup preserves the first copy of a block and elides the later ones, so the encoded text appears more than once by construction. Walking every message in order, replaced or not, keeps position and identity in agreement.

Where the caller's escaping differs from ours the text is equal after parsing but absent from the raw bytes. There the splice declines and the original body is forwarded: invariant 3's direction, since a lost saving costs tokens and a corrupted field costs correctness, and only one of those is recoverable by the caller. The same rule refuses any spliced body that is not smaller than the one that arrived — M7's third consequence, which nothing had ever asserted.

The metrics half was mild

Reported against measured on that payload: 48.5% claimed, 47.1% on the wire — directionally right, ~1.4pp optimistic, the gap being JSON structural overhead the render never sees and the provider always bills. Pointed at the forwarded body it now reads 46.3% against 46.5%.

Still counted in tokens, still through estimateBundleTokens. A saving denominated in bytes compared against a budget denominated in tokens is precisely the two-estimator defect DECISIONS §19 exists to prevent; what changed is the artefact measured, not the unit.

Reviewer note: the test file was green before it was right

test/integration/gateway-wire-metrics.test.ts passed on its first run — 4520 bytes in, 4520 out, tokensSaved: 0. session-dedup elides a block only once a previous turn has registered its hash, so a single-turn fixture elides nothing and every assertion holds vacuously. Each test now asserts the elision fired before asserting anything about it, and the file documents why.

That is the tenth instance of this project's oldest failure and the second in two sessions. Both recent ones were in new tests written to prove a fix — the moment the temptation to believe a pass is highest. Worth a look during review: the guard is expect(forwarded).not.toBe(rawBody) ahead of the substantive assertions.

Verification

  • 662 tests, typecheck, lint and build green.
  • Existing Gateway tests unchanged and still passing — including gateway-dedup-reality (the within-payload saving still works) and gateway-byte-fidelity.
  • End-to-end through the built dist/: all three numeric literals preserved exactly, 4582 → 2451 bytes, reported 46.3% against 46.5% actual.

Scope

max_audit.md is now closed in full. docs/audit-remediation-status.md §1, §6 and §7 are updated, and §6 keeps the record of how the item went missing — gated behind "only if question B keeps the Gateway", B answered in §41, nothing carrying it across — because the way an item disappears matters more than the item.

Not released. This changes forwarded bytes and reported metrics, so it is a minor when it ships.

🤖 Generated with Claude Code

ojassug and others added 3 commits August 12, 2026 23:24
Closes audit M7 -- the last open finding in max_audit.md.

All three of M7's consequences were live when measured. They are not the same
defect, and the re-serialization half is the one that mattered.

When an elision fired, the proxy rebuilt the request with JSON.stringify, which
rewrote fields it had never touched. Measured on a hand-written payload with one
elision -- the only shape the Gateway saves on:

  "temperature": 1.0             -> "temperature":1
  "top_p": 1e3                   -> "top_p":1000
  "seed": 12345678901234567890   -> "seed":12345678901234567000

The first two are cosmetic. The third is a different number: an integer past 2^53
does not survive JSON.parse -> JSON.stringify, so a provider was being asked for a
seed the caller never chose, by a proxy whose whole promise is faithfulness. This is
the mechanism the project already identified as the phantom -1.39% in the Python
harness (Issue 5), reproduced in production code.

Elided content is now spliced into the caller's own bytes: each message's content is
located by the canonical JSON encoding of the parsed text, searched forward from the
previous message's end, and only that span is replaced.

The forward cursor is the design, not an optimization. A first version searched
globally and refused ambiguous matches, which declined every payload the Gateway can
save on -- session-dedup preserves the first copy of a block and elides the later
ones, so the encoded text appears more than once by construction. Its tests passed,
because with the splice declining nothing changed and every assertion held.

Where the caller's escaping differs from ours, the text is equal after parsing but
absent from the raw bytes; there the splice declines and the original body goes out.
Invariant 3's direction: a lost saving costs tokens, a corrupted field costs
correctness. The same rule refuses any spliced body that is not smaller than the one
that arrived, which is M7's third consequence and had never been asserted.

The metrics half was mild. Reported against measured: 48.5% claimed, 47.1% on the
wire -- directionally right, about 1.4pp optimistic, the gap being JSON structural
overhead the render never sees and the provider always bills. Pointed at the
forwarded body it now reads 46.3% against 46.5%. Still counted in tokens through
estimateBundleTokens: a saving in bytes compared against a budget in tokens is the
two-estimator defect §19 exists to prevent.

The new test file was green before it was right -- 4520 bytes in, 4520 out,
tokensSaved 0, because session-dedup needs a previous turn to have registered the
block hash and a single-turn fixture elides nothing. Each test now asserts the
elision fired before asserting anything about it. Tenth instance of this project's
oldest failure, second in two sessions, and both were in new tests written to prove
a fix.

DECISIONS §54. 662 tests, typecheck, lint and build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
max_audit.md ends with a nine-row LOW table. The wave tables account for every
CRITICAL, HIGH and MEDIUM finding, and L1, L4, L5, L6, L7, L8 and L9 appear in
no wave, no decision and no row of the status doc. Verified against source: all
seven open. L2 and L3 were closed incidentally by the C2 Buffer work, which is
the tell -- the two that got fixed are the two that happened to sit inside
someone else's diff.

This is DECISIONS §54's failure mode three days later. §54 closed M7 and named
the rule -- an item that is in no table reads as done, exactly like a check that
never ran reads as a pass -- and naming it did not prevent the recurrence,
because the check being run was still "what did we work on?" rather than "what
does the document list?". Close a document against its own list of findings.

L7 was rated too low, and that is the interesting part. The audit calls it
"fails safe (skip) but silently loses the region", which reads like the cost is
one region. Measured end-to-end it is the whole file, because a one-region file
has nothing else to elide:

  normal.py       434 bytes -> 96 bytes   (77.9%)
  blank_first.py  436 bytes -> 436 bytes  (0%, fallback)

scanPythonDefBodies read the body indent off def+1 without asking whether that
line was blank. indentOf('') is 0, the region began at column 0, the marker
inherited column 0, PythonValidator reported AST_INDENTATION_ERROR, and
elideRegions skipped it as post_condition_rejected. The `last` scan in the same
function already skipped blanks; only the line reading the indent did not, so
the two disagreed about where a body starts.

The corpus cannot see this fix, and that is a fact about the corpus. Per-row
over the frozen 288-file corpus, 576 of 576 rows are byte-identical across all
fifteen compared fields -- because 0 of 45 Python corpus files have a blank line
in that position. pip internals and this repository's own Python are uniformly
PEP 8 there. Do not read 576/576 as "inert"; ask first whether the corpus
contains the shape. That is §52's corpus-bias caveat with the sign reversed.

L1 is §30 arriving by the other door. TOKENDAMPER_PLANNER_MODE=session_dedup was
silently discarded while --planner-mode session_dedup threw, and session_dedup
is a real member of OptimizationMode, so a user had every reason to think it
took effect. All four enum parsers now reject through one helper rather than one
being fixed and three keeping the trap. Accepted sets are unchanged; widening
defaultMode past pass_through is left as the separate question it is.

L4, L5 and L9 are recorded at their sites rather than changed. L4's premise does
not hold: it says the hash stops being a hash of item.content, implying it was
one, and on the route that reaches that stage it never was -- createContextBundle
hashes a provenance object and sets id to it. The narrower defect (the value is
chained) is real and unreachable, since the one consumer treating it as a content
identity is session-dedup, which runs only under a planner mode where
constraint-preservation is not planned. Changing it moves bundle.contentHash and
every pinned id in the suite while moving no output byte. L5's minimum is correct
for a safety gate and the doc comment was what was wrong. L9's 12-hex prefix is
ample for provenance and birthday-bound for identity, and is not widened because
TokenHasher.resolve already treats an ambiguous prefix as unresolvable.

Also removes three dead Gateway metric fields. GatewayOptimizationOutcome still
computed rawTokens/optimizedTokens/tokensSaved from summary.tokenEstimate after
wireTokenMetrics replaced them at both call sites (§54). The two disagree by
design -- 48.5% against 47.1% on the same payload -- and a field that looks
authoritative next to the one that replaced it is how a closed finding comes back.

Every fixed case was run against the unfixed engine first: 5 of 7 assertions fail
there, and the 2 that pass are the negative controls, which must pass both ways
or they are testing nothing.

max_audit.md is now closed in full. DECISIONS §55, status doc §8.
669 tests, typecheck, lint and build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The document still ran on v1.4.0 as its baseline and carried M7 as open in three
places -- the v1.1.x follow-on list, the v2.0.0 gate notice and the Version
Summary row -- after §54 closed it. v1.5.0 had shipped with no section of its own,
though v1.3.0 and v1.4.0 both have one.

- v1.5.0 gets its section and becomes the baseline; v1.4.0 moves to prior release.
- An unnumbered row records §54 and §55 as merged and unreleased. No number is
  reserved for it: §53's rule is that a number is a fact about what shipped,
  assigned at ship time, after four reservations in four releases turned out wrong.
- The three M7-open notices are retired. The v2.0.0 sequencing constraint is split
  rather than deleted, because only half of it is satisfied: metrics now measure
  the bytes forwarded, so a /metrics endpoint would export a number that means
  what it says, but the premise still stands -- a Prometheus endpoint on a
  pass-through that saves nothing cross-turn instruments nothing. What is worth
  exporting today is within-payload dedup and the fallback rate, both real.
- The gate notice records that "every finding is closed" was written there twice
  before it was true, and why both misses look identical from the inside.
- "What to do instead" is down to one live item. Sub-region elision shipped (§50),
  per-item drift closed unbuilt (§51) after its precondition expired, and M7 is
  done (§54). Widening elision beyond TypeScript/JavaScript/Python is what remains
  and is the largest measured gain available -- every other corpus bucket is
  0.00%. The entry now carries the warning that it is not one gate despite
  supportsRegionElision being one function: a new language needs a validator and
  extractSymbols coverage as well as a region scanner, and adding the scanner
  alone converts a 0% into a fallback via §33's measurement gate.
- The 2026-08-07 measured-position table is marked historical. It read as "the
  numbers any of the above will be judged against" while every figure in it has
  since moved; the live baseline is the status doc §2, re-measured per release.

No feature work started. The gate is open and the repository is ready for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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