Skip to content

audit C4: structure is not a string, and it was not latent - #12

Merged
ojassug merged 1 commit into
mainfrom
audit-c4
Aug 10, 2026
Merged

audit C4: structure is not a string, and it was not latent#12
ojassug merged 1 commit into
mainfrom
audit-c4

Conversation

@ojassug

@ojassug ojassug commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Closes audit C4 — the last unstarted audit item. Full reasoning in DECISIONS §45.

The finding, and the part of it that was wrong

The Gateway ingests a provider message as JSON.stringify(msg.content) and wrote the optimized
item back as a plain string regardless of what the caller sent. A message whose content was
[{"type":"tool_result","tool_use_id":"toolu_01ABC",…}] could reach the provider as a bare
string — a 400 invalid_request_error on Anthropic, broken multimodal parts on OpenAI.

The audit rates this "Critical when reached (currently masked by H1)" and says the payload falls
back unchanged — "that is luck". Measured, the mask covers one case and not the other, and the
other is the one that matters:

case drift outcome
cross-turn sole copy recoverable: false, scored in full falls back — genuinely masked
duplicated within one payload recoverable: true, exempt by substitution no fallback — it shipped

On the pre-fix engine:

fallbackUsed: false   tokensSaved: 42
messages[2].content = "{\"__td_block__\":\"[TokenDamper Elided: ref=17c67ba215e4 bytes=251 kind=conversation]\"}"

Within-payload duplication is also the only case the Gateway saves anything on at all
(§41, gateway-dedup-reality.test.ts). So C4 was live on precisely the path the mode exists for.

This is the third audit reachability claim corrected by measurement (§40, §42, §45). The
findings have held up well; the "is it reachable" calls have not, because reachability here
depends on interactions between the drift exemption, the planner mode and the stage list that are
not local to the code being read.

What changed

1. The refusal is at the chokepoint, not in the Gateway. core/elision gains
CONTENT_SHAPE_METADATA_KEY and hasStructuredContent; elideItem and elideRegions refuse an
item tagged contentShape: 'structured' with a new ElisionSkipReason, 'structured_content'.
Putting it in the Gateway would protect today's single stage; putting it here protects any stage
the Gateway is ever pointed at.

The union member failed to compile in all three eliding stages until each acknowledged it —
the intended forcing function, and why it is a union member rather than a boolean. The check runs
first: a structured item stringifies to JSON and would usually have been refused downstream
anyway — for being JSON, which is right outcome and wrong reason. Untagged items are plain text,
so CLI, MCP and bench are untouched.

2. Egress maps by slot, not array position. if (!msg) continue on ingestion against
finalBundle.items[idx] on egress meant one hole in messages shifted every later item onto the
wrong message. Also not masked — the pinning test fails against the pre-fix engine with
expected 'ok' to be 'export function helper0…'.

3. The Anthropic system prompt is mapped back. It was ingested as items[0] while egress
started at itemOffset, so a change was dropped from finalBody while optimizedTokens — and
therefore tokensSaved — still counted it as saved. This path is unreachable today and the
docs say so rather than claiming a fix that fires; what it buys is a correct mapping for when
something does change one, plus a guard on the finalBody rebuild, which is a live regression
risk this PR creates.

Verification

  • 566 tests passing (was 557), typecheck and lint clean.
  • Four of the nine new tests fail against the unfixed code. The other five are controls and
    guards on genuinely-masked paths — the test file states which is which, so the distinction
    isn't rediscovered later.
  • 594 of 594 corpus rows identical to the pre-C4 engine across 17 fields, same frozen corpus,
    varying only dist/. The guard is inert for untagged items — measured, not reasoned.
  • Live on a real GatewayServer, the discriminating pair (identical bytes, differing only in
    shape):
structured            fallbackUsed: false   tokensSaved: 0    stays an array
same bytes as string  fallbackUsed: false   tokensSaved: 42   elided normally

One corpus caution

TypeScript file reads 23.16% here against Wave 2's 23.26% — same recipe, same counts — and
the pre-C4 engine also reads 23.16%, so it is not this change. The cause is line endings:
Wave 2's corpus was frozen from working-tree files written with LF, and committing normalized them
to CRLF, adding a byte per line to the repo's own sources, which are the corpus. Aggregate
figures are not comparable across a commit boundary, only across a corpus-size change as
previously assumed. Recorded in §45 and the status doc's traps.

Docs

DECISIONS.md §45 · CHANGELOG.md · docs/audit-remediation-status.md (C4 closed, its stale
"still masked" premise corrected, four new traps) · CLAUDE.md · ROADMAP.md.

Every task-shaped audit item is now closed. What remains is three decisions (H2, M1, M11) and
the architectural work in the status doc's §5 — chiefly Phase 1c.

🤖 Generated with Claude Code

Closes audit C4, the last unstarted audit item. Full reasoning in DECISIONS §45.

The Gateway ingests a provider message as `JSON.stringify(msg.content)` and wrote
the optimized item back as a plain string regardless of what the caller sent. A
message whose content was `[{"type":"tool_result","tool_use_id":"toolu_01ABC",…}]`
could reach the provider as a bare string — a 400 invalid_request_error on
Anthropic, and broken multimodal content parts on OpenAI. Tool-heavy traffic is
the entire target market.

The audit rates this "Critical when reached (currently masked by H1)" and says the
payload falls back unchanged — "that is luck". Measured, the mask covers one case
and not the other, and the other is the one that matters. A cross-turn sole copy
is `recoverable: false`, scored in full by drift, and does fall back. Content
duplicated within one payload is elided `recoverable: true`, which DriftTracker
exempts by substitution (§16) — no drift, no fallback, and it ships. On the
pre-fix engine:

  fallbackUsed: false   tokensSaved: 42
  messages[2].content = "{\"__td_block__\":\"[TokenDamper Elided: ref=17c67ba215e4 bytes=251 kind=conversation]\"}"

Within-payload duplication is also the only case the Gateway saves anything on at
all (§41, gateway-dedup-reality.test.ts), so C4 was live on precisely the path the
mode exists for. This is the third audit reachability claim corrected by
measurement (§40, §42, §45); the findings hold up, the "is it reachable" calls
have not, because reachability here depends on interactions between the drift
exemption, the planner mode and the stage list that are not local to the code
being read.

Three changes.

1. The refusal lives at the chokepoint, not in the Gateway. `core/elision` gains
`CONTENT_SHAPE_METADATA_KEY` and `hasStructuredContent`; `elideItem` and
`elideRegions` refuse an item tagged `contentShape: 'structured'` with a new
`ElisionSkipReason`, `'structured_content'`. Putting it in the Gateway would
protect today's single stage; putting it here protects any stage the Gateway is
ever pointed at, which is the change most likely to make this live again. The
union member failed to compile in all three eliding stages until each
acknowledged it — the intended forcing function, and why it is a union member
rather than a boolean. The check runs first, before the savings and syntax
checks: a structured item stringifies to JSON and would usually have been refused
downstream anyway, for being JSON, which is right outcome and wrong reason.
Untagged items are treated as plain text, which is correct for CLI, MCP and bench.

2. Egress maps by slot, not array position. Ingestion skips falsy entries with
`if (!msg) continue` while egress indexed `finalBundle.items[idx]`, so one hole in
`messages` shifted every later item onto the wrong message. Also not masked: the
pinning test fails against the pre-fix engine with `expected 'ok' to be 'export
function helper0…'`. Invariant 9 says the mapping is faithful, not that it is
positional, and a filtered push cannot supply a positional map's precondition.

3. The Anthropic `system` prompt is mapped back. It was ingested as `items[0]`
while egress started at `itemOffset`, so a change to it was dropped from
`finalBody` while `optimizedTokens` — and therefore `tokensSaved` and
`dedupRatio` — still counted it as saved. This path is unreachable today
(session-dedup refuses system items, and rehydration needs `rehydrateRefs`, which
the Gateway does not set) and the docs say so rather than claiming a fix that
fires; what it buys is a correct mapping for when something does change one, and
a guard on the `finalBody` rebuild, which is a live regression risk this commit
creates.

Measurement: 594 of 594 corpus rows identical to the pre-C4 engine across 17
fields, same frozen corpus, varying only dist/ — the guard is inert for untagged
items, measured rather than reasoned. Live on a real GatewayServer, the
discriminating pair (identical bytes, differing only in shape):

  structured            fallbackUsed: false   tokensSaved: 0    stays an array
  same bytes as string  fallbackUsed: false   tokensSaved: 42   elided normally

Suite: 566 passing (was 557), typecheck and lint clean. Four of the nine new tests
fail against the unfixed code; the other five are controls and guards on masked
paths, and the test file says which is which.

One corpus caution recorded in §45 and the status doc: TypeScript file reads
23.16% here against Wave 2's 23.26%, same recipe and same counts — and the pre-C4
engine also reads 23.16%. The cause is line endings. Wave 2's corpus was frozen
from working-tree files written with LF, and committing normalized them to CRLF,
adding a byte per line to the repo's own sources, which are the corpus. Aggregate
figures are not comparable across a commit boundary; only the per-row A/B over one
frozen corpus is.

Every task-shaped audit item is now closed. What remains is three decisions (H2,
M1, M11) and the architectural work in the status doc's §5, chiefly Phase 1c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ojassug
ojassug merged commit c61d518 into main Aug 10, 2026
3 checks passed
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