Skip to content

refactor(outbound): judge the request edge in the orchestrator - #569

Merged
fatherlinux merged 1 commit into
mainfrom
refactor/phase-5-request-edge
Oct 27, 2026
Merged

fatherlinux merged 1 commit into
mainfrom
refactor/phase-5-request-edge

Conversation

@fatherlinux

Copy link
Copy Markdown
Member

What

Moves judge_outbound + OutboundVerdict out of gateway/outbound.py onto the request edge in orchestrator.py. The outbound (/proxy/llm and terminating egress) request path's defend() call and refuse decision now live in the orchestration layer, beside judge_response / judge_egress_response. gateway/outbound.py keeps the pure request-reading helpers (reveal, judge_input, body_text, detect_credentials).

One fewer defend() caller in gateway/ — the Phase 5 "defend() → one caller (the orchestrator)" target.

The drift this deletes

OutboundVerdict.blocks() was a hand-rolled kinds or flagged or unjudged — a second copy of the layer refusal rule. It's replaced by a refused field decided in judge_outbound, whose layer part goes through refusal_reason() — the one rule dispose() itself delegates to. The credential dimension (outbound-only, outside dispose()'s model) is OR'd on top, gated to block.

Byte-identical

DefenseVerdict.flagged is defined as flagged_by is not None (defense.py:148), so refusal_reason(flagged_by, gaps) is non-None in exactly the cases the old blocks() refused. The exception/deadline path is unchanged (no verdict ⇒ unjudged ⇒ block refuses, flag warns).

Test seam

judge_outbound keeps its function-local from .defense import defend, so the REQUEST edge is still patched at trentina.defense.defend, distinct from the RESPONSE edge's trentina.orchestrator.defend (the split test_egress_http relies on). It keeps a lazy gaps_of so the request edge resolves gaps in the modes namespace — deliberately not via dispose(), which would couple its gap derivation to the response edge's core.dispose namespace in a combined crossing. No test gaps-patch changes were needed.

Verification

  • ruff / mypy / semgrep (0) / Gourmand (0) / Gatehouse (staged diff) all green.
  • Targeted suites green: test_llm_proxy, test_egress_http, test_orchestrator, test_disposition_parity, test_defense_contract, test_gatekeeper, test_log_hygiene (248 passed).
  • Full suite 8140 passed; the lone failure is a pre-existing process-global (PYTHONHASHSEED-sensitive) flake — clean main shows the same 1-failure pattern with a different OAuth/secret victim, and both victims pass in isolation.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K4tUpQP4psJxsQWixULwQj

Moves judge_outbound + OutboundVerdict out of gateway/outbound.py onto the
request edge in orchestrator.py, so defend() on the outbound (/proxy/llm and
egress) request path is reached from the orchestration layer rather than the
gateway. gateway/outbound.py keeps the pure request-reading helpers (reveal,
judge_input, body_text, detect_credentials); the defend() call and the refuse
decision ride with the orchestrator next to judge_response / judge_egress_response.

OutboundVerdict gains a `refused` field decided in judge_outbound, replacing the
hand-rolled OutboundVerdict.blocks() = `kinds or flagged or unjudged`. The layer
part now goes through refusal_reason() -- the one rule dispose() delegates to --
rather than a second copy of `flag or gap`; the credential dimension is OR'd on
top, gated to block. Byte-identical: DefenseVerdict.flagged is defined as
`flagged_by is not None`, so refusal_reason(flagged_by, gaps) is non-None in
exactly the cases the old blocks() refused.

judge_outbound keeps its function-local `from .defense import defend` so the
REQUEST edge is patched at trentina.defense.defend, distinct from the RESPONSE
edge's trentina.orchestrator.defend (the seam test_egress_http relies on), and
its lazy gaps_of so the request edge resolves gaps in the modes namespace,
independent of the response edge's dispose()/orchestrator namespaces.

One fewer defend() caller in gateway/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K4tUpQP4psJxsQWixULwQj
@fatherlinux
fatherlinux enabled auto-merge October 27, 2026 14:14

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gatehouse found no issues.

Full review.

1 finding(s) dropped: their evidence is not in the diff.

@fatherlinux
fatherlinux merged commit 7666cff into main Oct 27, 2026
22 checks passed
@fatherlinux
fatherlinux deleted the refactor/phase-5-request-edge branch October 27, 2026 14:38
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