Skip to content
This repository was archived by the owner on Aug 11, 2026. It is now read-only.

fix(relay): forward the platform's rejection detail to the harness - #125

Merged
Peyton-Spencer merged 1 commit into
mainfrom
peyton/forward-platform-denial-detail
Jul 28, 2026
Merged

fix(relay): forward the platform's rejection detail to the harness#125
Peyton-Spencer merged 1 commit into
mainfrom
peyton/forward-platform-denial-detail

Conversation

@Peyton-Spencer

@Peyton-Spencer Peyton-Spencer commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

A pre-reservation 4xx from the platform reached the harness as the fixed string "inference request denied" — even though the platform's own explanation was sitting in responseBody at that exact site. Combined with the platform saying only "unsupported inference parameter" without naming the parameter, a miner whose request carried one unrecognised field had no channel at all to learn its name.

That is not hypothetical. Cooking (rank 15 on v1) burned three submissions on a single unrecognised reasoning key — v3 shipped byte-identical to v2 on that line — because nothing anywhere in the stack ever said the word "reasoning".

This is the last hop. ditto-assistant/ditto-platform#554 makes the platform name the key; this PR makes the name survive to the harness's stderr.

Change

platformRejectionMessage(body []byte) string reads message out of the platform's error envelope ({error_code, message, request_id}, per middleware/error_envelope.py) and is used at the agentRequestRejections site in forwardChatCompletion. It goes to both destinations that matter: the harness's stderr and the operator's log line, which previously reported only the status code.

Hardening, because this string crosses a trust boundary into a log:

  • Control characters stripped. It lands in a log.Printf and a harness's stderr; a newline would let a relayed message forge a log entry.
  • Bounded at 400 bytes, truncated on a valid-UTF-8 boundary with an explicit .... Long enough for a schema refusal naming several fields, short enough that nothing can pump bulk text into either destination.
  • Bodies over 64 KiB are not parsed at all.
  • Empty, blank, or unparseable → falls back to the old wording, so a platform that sends no envelope behaves exactly as before.

Classification is deliberately untouched

This is the fleet-skew constraint, and it is the reason this change is safe to ship against validators running 0.34.1 through 0.37.3 concurrently:

  • Same HTTP status (the platform's own, forwarded unchanged).
  • Same counters — usageUnavailable++, agentRequestRejections++.
  • Same fault attribution: agent_request_rejections. These are the harness's own bytes, so that remains the correct bucket. Nothing moves to a no-fault code — no new validator_infrastructure code is introduced, so there is nothing for ditto-subnet to map to scoring_error and no risk of charging miners on older validators.
  • Same response shape: still {"error": …}. Only the human-readable value differs, which is why this cannot break an existing harness.

TestASchemaRejectionNamesTheFieldToTheHarness asserts every one of these explicitly alongside the new behaviour.

Tests

  • TestPlatformRejectionMessageExtractsTheExplanation — pulls the field name out of a real envelope, including the multi-field case.
  • TestPlatformRejectionMessageFallsBackOnAnythingUnusable — 7 malformed/empty bodies plus an oversized one all yield the fallback.
  • TestPlatformRejectionMessageCannotForgeLogLinesOrRunLong — newline injection is neutralised; a 2000-byte message is bounded and says it was truncated.
  • TestASchemaRejectionNamesTheFieldToTheHarness — end to end on the exact body that broke Cooking: harness sees 400, the {"error": …} shape, the platform's own string, and the word reasoning; counters and attribution unchanged.
  • TestAnUnparseableRejectionKeepsTheOldWording — an older platform is unaffected.

go build ./..., go vet ./..., and the full go test ./... suite pass.

Related

🤖 Generated with Claude Code

A pre-reservation 4xx from the platform reached the harness as the fixed
string "inference request denied", even though the platform's own
explanation was sitting in responseBody at that exact site. Combined with
the platform saying only "unsupported inference parameter" without naming
the parameter, a miner whose request carried one unrecognised field had no
channel at all to learn its name.

`Cooking` burned three submissions on a single unrecognised `reasoning`
key -- v3 shipped byte-identical to v2 on that line -- because nothing in
the stack ever said "reasoning".

platformRejectionMessage reads `message` out of the platform's error
envelope, strips control characters (this string lands in a log line and a
harness's stderr, so a newline could forge an entry), and bounds it at 400
bytes. An unparseable or empty body falls back to the old wording, so an
older platform is unaffected.

Classification is deliberately untouched: same status, same counters, same
agent_request_rejections bucket, same {"error": ...} response shape. Only
the human-readable string differs, which is what makes this safe across
the 0.34.1-0.37.3 validator fleet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Peyton-Spencer Peyton-Spencer changed the title peyton/forward platform denial detail fix(relay): forward the platform's rejection detail to the harness Jul 28, 2026
@Peyton-Spencer
Peyton-Spencer marked this pull request as ready for review July 28, 2026 17:28
@Peyton-Spencer
Peyton-Spencer merged commit 1d07f52 into main Jul 28, 2026
6 checks passed
@Peyton-Spencer
Peyton-Spencer deleted the peyton/forward-platform-denial-detail branch July 28, 2026 17:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant