Repository navigation
Conversation
8c0b4cb to
0d86e94
Compare
hidb4ai
left a comment
There was a problem hiding this comment.
Three confirmed issues in enforcing protection at this head: implicit Responses storage, uninspected historical item references, and a swallowed privacy refusal during Memory flush. Reproductions are included inline.
hidb4ai
left a comment
There was a problem hiding this comment.
Rechecked current head 4c645eab3a781ca177424394f64a1a8127a6b3a4, which is unchanged from the previous review. All three existing findings still reproduce:
- Implicit Responses storage: omitting
storestill receives complete admission; the native SDK also sends it omitted. - Unexpanded item references: an
item_referencestill receives complete admission without inspecting the referenced content. - Swallowed Memory gate refusal: with enforcing protection and a real loopback model endpoint, the forbidden gate response raises a privacy refusal on direct evaluation, but
memory.flush()still writes the Memory and consumes Source position 1. Reopening SQLite preserves the write and shows no pending work.
A read-only merge against current master (aedf7beb) also reports conflicts in six files, including Desktop wire/IPC contracts. The SeekDB vector-upgrade check and its aggregate release gates remain failed; I have not established the root cause of that CI failure.
The existing findings remain unresolved, so this is not ready to merge.
hidb4ai
left a comment
There was a problem hiding this comment.
Re-reviewed this head. The implicit Responses storage and Memory gate-refusal findings are fixed in the exercised paths. The historical-reference check still admits omitted/null discriminators; I added the reproduction to the existing thread. There is also a deterministic evaluation CI regression detailed inline.
hidb4ai
left a comment
There was a problem hiding this comment.
One additional confirmed protection bypass at e51bb4a: opaque Anthropic document sources receive complete admission and pass through the owned transport. Details and a public-API/native-SDK reproduction are inline. The updated Responses-reference finding and evaluation regression already have threads, so they are not duplicated here.
hidb4ai
left a comment
There was a problem hiding this comment.
Re-reviewed this head. The prior privacy findings and evaluation usage-compatibility regression pass the current probes. One deterministic merge-tree integration failure remains in the generated Cindy bundle; details are inline.
| commit_connector_checkpoint: { method: 'POST', path: '/v1/connector-checkpoints/commit', location: "body", scopeMode: 'none', pathParameters: [], queryParams: [], headerParams: [], successStatuses: [200], emptyStatuses: [] }, | ||
| query_code: { method: 'POST', path: '/v1/scopes/{scope_id}/code/query', location: "body", scopeMode: 'current', pathParameters: ['scope_id'], queryParams: [], headerParams: [], successStatuses: [200], emptyStatuses: [] }, | ||
| prepare_context: { method: 'POST', path: '/v1/context/prepare', location: "body", scopeMode: 'current', pathParameters: [], queryParams: [], headerParams: [], successStatuses: [200], emptyStatuses: [] }, | ||
| check_context: { method: 'POST', path: '/v1/context/check', location: "body", scopeMode: 'current', pathParameters: [], queryParams: [], headerParams: [], successStatuses: [200], emptyStatuses: [] }, |
There was a problem hiding this comment.
[P2] Regenerate the Cindy bundle when integrating with current master
This added operation also enters Cindy's standalone worker through its shared DSH client. On the merge tree with current master (838f726c), rebuilding integrations/cindy/plugins/powercontext-hooks/node/worker.cjs adds the check_context block, but the checked-in bundle lacks it. The required git diff --exit-code in make cindy-test therefore fails before the package tests, matching the failed cindy-package job. I reproduced a clean rebuild on master alone and the exact 11-line drift after combining it with this PR. Please integrate the current Cindy files, regenerate and commit the worker, and rerun its package gate.
There was a problem hiding this comment.
@hidb4ai Independently confirmed against the exact CI checkout: rebuilding Cindy at base bf2ac1fcd leaves the bundle unchanged, while rebuilding merge commit c8f3783d (PR head 1388c069) adds exactly the 11-line check_context operation block. Both runs used the locked tsdown 0.16.8 / rolldown 1.0.0-beta.52 toolchain. The required bundle-diff gate therefore fails deterministically before package tests. Please regenerate and commit integrations/cindy/plugins/powercontext-hooks/node/worker.cjs when integrating the Cindy files, then rerun cindy-package before merging.
* feat(gateway): add Rust PowerContext Gateway executor Two-phase check executor (before_send / before_release) with opaque bindings, prepare-based memory injection with prepared_contexts byte range declaration, synchronous sedimentation via capture_content_source, UDS/TCP dual-form CoreLink with pooled UDS client, port-takeover proxy, OpenAI chat/responses ingress, and a composable middleware chain (resource limits, request id). 27 tests. Depends on the context-protection contract in #2264; design follows #2199. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * style(gateway): clippy clean — desugar impl-Future wrappers to async fn Trait declarations keep RPITIT with explicit Send bounds (public API); implementations use async fn per clippy. Also fixes redundant closures, needless as_bytes, useless conversion, unused imports, and simplifies FakeGate test doubles. cargo clippy --all-targets: zero warnings; 27 tests pass. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * docs(gateway): document features and the PC UDS call sequence Adds the per-request feature set (gates, injection, sedimentation, degradation, streaming, middleware) and the five-step UDS call sequence (prepare → check before_send → send → check before_release → capture) to the gateway README. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): harden gate semantics from code review - Degrade (passthrough) now applies only to GateUnavailable; a Malformed gate response always pauses — a reachable-but-misbehaving Core must never be bypassed (fail-closed). Guarded by test. - Strict UTF-8 instead of lossy conversion in all three payload paths: invalid renderer output blocks before send; invalid upstream responses are withheld (never converted, never released); sediment reuses the validated text. Closes the check-payload vs sent-bytes fidelity gap. - serialize with expect (infallible, explicit) instead of silent empty body; default scope read once at startup instead of per request; extract read_with_timeout helper in the UDS client. Verified end-to-end against a real PowerContext Server over UDS with both DeepSeek and GLM upstreams: per turn exactly check x2, prepare x1, capture x1; forbidden content still blocked with zero sends. cargo clippy --all-targets: zero warnings; 29 tests. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * chore(gateway): add Apache-2.0 license headers to all source files Adds the repository-standard Apache-2.0 header (copyright OceanBase, per .licenserc.yaml) to all 38 files: 30 Rust sources (// comments) and 8 Cargo.toml manifests (# comments), matching the style used by the Python sources. Resolves the license-check CI failure. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): never retry requests already handed to the upstream reqwest::Error::is_request() also covers a connection closing while awaiting response headers after the complete body was sent, so the previous mapping could retry an already-delivered (billable) body and then report AllNotSent. NotSent is now restricted to failures proven to precede transmission (is_connect / is_builder); every post-handoff error classifies as Indeterminate and is not retried. Regression test reproduces the review scenario: a loopback upstream that reads the full POST body and closes — with BoundedRetry (max_attempts=2) the server observes exactly one body and the outcome is Indeterminate. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): disable automatic upstream redirects reqwest's default redirect policy would re-send an admitted POST body (307/308 preserve method and body) to the Location target without any Core admission — the approved destination silently stops being the actual destination. Both HTTP clients (upstream transport and gate client) now set redirect Policy::none(); a 3xx response surfaces as a normal delivered response (status passed through to the caller after the release check) and no bytes are ever re-sent automatically. Regression test reproduces the review scenario: an approved loopback upstream returning 307 to a second listener — the redirect target receives zero bytes and the 307 is delivered as-is. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): preserve Chat tool-call metadata through canonicalization The ingress DTO dropped assistant tool_calls and the tool message's tool_call_id, and rendered content:null as an empty array — an invalid continuation that breaks any function-calling client. CanonicalMessage now carries tool_calls, tool_call_id and a content-null marker; the chat renderer emits content:null with the verbatim tool_calls array and preserves the pairing id. Responses items are unaffected. Tests: round trip reproduces the review probe (assistant with content:null + tool_calls, tool message with tool_call_id reaches the wire intact) plus ingress canonicalization units. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): keep Responses text content in a valid wire shape The Responses renderer serialized single-string message content as a bare string array (["hello"]), which is not a valid Responses content form (string or typed content parts). Single-string content now stays a string; typed-part arrays pass through unchanged; content:null is preserved. The existing renderer test now asserts the content shape, and a typed-parts passthrough test was added. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): apply the response buffer limit to nonstreaming calls The nonstreaming path collected the whole response with resp.bytes() and never applied max_buffer_bytes — the advertised bounded buffering was ineffective on the usual nonstreaming path, and the response was fully allocated before any release check. Both response modes now share the incremental chunked accumulation with the byte cap; the only difference is TTFT measurement, recorded for streaming requests. Exceeding the limit stops reading and classifies as Indeterminate(BufferLimitExceeded). Regression tests on a real socket: with_buffer_limit(16) rejects a 1 KiB nonstreaming response; a within-limit response still delivers. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * style(gateway): drop narrative comment at the redirect policy Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): enforce the configured body limit on chunked requests The middleware precheck only inspects Content-Length, so the same oversized body sailed through with chunked framing while the ingress consumed it under a separate hard-coded 32 MiB cap. The ingress now consumes the body under the configured PC_GATEWAY_MAX_BODY_BYTES and maps the length-limit error to 413 (same semantics as the precheck), so callers cannot bypass the admission limit by changing HTTP framing. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * chore: remove accidentally committed stale gateway copy powercontext/gateway/ was a stale snapshot introduced by 5a89508: a relative-path rsync ran with the shell cwd drifted inside the repo, copying the workspace into a nested powercontext/ subdirectory that was then committed alongside the intended top-level gateway/. The copy carried no workspace manifest, participated in no build, and lacked the subsequent fixes (b87c096 duplicate-send, 5a89508 redirects were partially present, 301d54e buffer limit, a0b9cb0 chunked limit), leaving ~4.9k lines of misleading dead code. Adds a .gitignore entry to prevent the same shape from reappearing. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * ci(gateway): build, lint, and test the Rust workspace Adds .github/workflows/gateway.yml (fmt --check, clippy -D warnings, cargo test --locked; ubuntu, no matrix, path-triggered on gateway/**) following the desktop.yml pattern with a pinned 1.95.0 toolchain and Swatinem/rust-cache, and registers a gateway suite in .github/ci-paths.toml so changes/detect selects it. Cargo.lock is now committed (--locked builds) and the tree is rustfmt-formatted. Note: ci-paths.toml was reworked by #2285 on master; this additive suite entry may need a small rebase once this branch rebases onto current master. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * ci(gateway): adopt the post-#2285 ci-paths structure Replaces the pre-#2285 edit with current master's .github/ci-paths.toml as the base, keeping only the additive gateway suite entry in [suites], so the branch merges cleanly against master. Wiring a gateway root job into master.yml's result gate (the desktop-validation pattern) is left as a follow-up to keep this PR's footprint in the heavily-churned master.yml minimal. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): enforce admission validity and safe degradation (P1-3, P1-4) P1-3: Degraded sends now use the pristine request snapshot taken before enrich(), so recalled private memory from a successful prepare never reaches the upstream in a degraded (unchecked) send. Degradation eligibility is resolved at startup from capabilities.context_protection.enforcing (fail-closed when unreachable), overriding any environment-variable request. P1-4: The executor now enforces valid_until at every boundary: before each send attempt (expired admission → AdmissionExpired with zero bytes sent), before release including after synchronous capture (expired release verdict → content withheld), and obtains fresh admission for each retry (deterministic NotSent re-enters check so Core revocations are noticed before the next attempt). correlate_until is never used for send or release decisions. Adds zero-dependency RFC3339 parsing with full test coverage. The retry loop moves from BoundedRetry into the executor so that re-admission can happen between attempts; BoundedRetry remains for transport-level use only. Four new probe-reproducing tests: degraded-without-injected-memory, expired-send-verdict, retry-re-admits-revocation-blocks, release-expired-after-capture. cargo clippy --all-targets: zero warnings; 47 tests. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): CI checks entry, fail-closed valid_until, capture re-check, capabilities timeout - [checks] gateway entry fixes the ci_changes.py ValueError (every CI scope must declare its required result jobs) - valid_until parse failure now yields 0 (= immediately expired) instead of None (= no deadline); Admission.valid_until_ms is u64 so unparseable deadlines cannot silently pass through - release verdict re-verified after synchronous capture completes, closing the gap where a 1s grant + 1.5s capture returned Released 551ms past expiry - capabilities request bounded by a 10s tokio timeout and 3s connect timeout; a stalling Core can no longer prevent Gateway startup 47 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * ci: adopt current master ci-paths.toml with gateway entries Rebases .github/ci-paths.toml on the latest master (post-#2285 and subsequent changes), preserving the two additive gateway entries ([suites] and [checks]) so the branch merges cleanly. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): preserve supported Chat request controls through rendering The ingress silently dropped max_completion_tokens, response_format, and tool_choice from accepted requests. The budget fallback (unwrap_or(1024)) inflated the output limit, the structured-output constraint was removed, and tool_choice:'none' was replaced by default automatic tool selection. CanonicalRequest now carries response_format and tool_choice as pass-through fields; the chat renderer emits them; max_completion_tokens takes priority over max_tokens. The Responses DTO also captures both fields. 47 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): Responses structured output via text field, not response_format The Responses API does not have a top-level response_format (that is a Chat Completions field); structured output lives in text: { format: ... }. The Responses DTO now captures 'text' instead, and the Responses renderer emits it as 'text' rather than 'response_format'. The Chat renderer still emits response_format correctly. Added a wire-shape test asserting text.format reaches the upstream payload and response_format does not. 48 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * test(gateway): probe tests for unparseable valid_until and capture-expiry Adds three tests closing the coverage gaps from review: - empty_valid_until_never_sends: valid_until:"" → parse fails → fail-closed (AdmissionExpired, zero sends) - malformed_valid_until_never_sends: space-separated datetime, garbage strings → same fail-closed behavior - capture_duration_expires_deadline_withholds_release: short-lived release grant checked against the post-capture re-verification path 50 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): apply the configured body limit to the proxy path The UDS fallback proxy forwarded unbounded bodies directly, so chunked-framed requests to non-/gateway routes (e.g. /v1/context/ prepare) bypassed the middleware's Content-Length precheck entirely. The proxy now wraps the request body in http_body_util::Limited with the same PC_GATEWAY_MAX_BODY_BYTES value used by the middleware and ingress; exceeding the limit truncates the body, causing the upstream to receive an incomplete request and fail rather than silently accepting an oversized payload. Streaming (SSE) responses are unaffected — only the request body is limited. 51 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): preserve Responses store field and restrict reuse to memory-only assembly P1 (store): The Responses DTO now captures the caller's store field (#2313 requires explicit store:false for openai-responses targets) and the renderer emits it. Without this, the /gateway/v1/responses route returned 409 provider_state_unsupported against the #2313 Core. P2 (assembly): The prepare request now specifies a memory-only assembly ({sections:[{family:'memory',limit:8}]}) so Core's default preparation no longer selects Experience/Topic families that #2313 rejects for reuse. Adding an Experience to a scope no longer silently disables all gateway memory injection. 51 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * test(gateway): probe tests for store field and memory-only assembly Adds three tests for the latest two fixes: - store_false_reaches_responses_wire_body: store:false must reach the Responses wire body (#2313 contract) - store_absent_omitted_from_wire_body: no store field when caller doesn't provide one (no fabricated defaults) - prepare_requests_memory_only_assembly: ContextAssembly::memory_only() produces the exact #2313 contract shape 54 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): hold concurrency permit for streaming responses; validate complete allow P2 (concurrency): The middleware released the concurrency permit when response headers arrived, but proxied MCP/SSE bodies continue streaming after headers. The permit is now attached to the response body via a PermitBody wrapper (implements http_body::Body holding the permit); it releases when the body completes, fails, or the connection drops, so active streaming connections count against PC_GATEWAY_MAX_CONCURRENT. P2 (allow validation): The executor accepted a check allow without verifying schema, phase match, coverage completeness, or non-empty binding. A controlled Core peer returning allow with incomplete coverage, missing binding, or wrong phase caused the gateway to send upstream and return 200. A validate_allow function now checks all four aspects before every send/release (including retry re-admission); malformed approvals pause the operation (fail-closed). The missing-binding fallback is removed entirely. 54 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * test(gateway): probe tests for malformed allow validation Five tests covering the P2 review finding: allow with incomplete coverage, missing binding, wrong phase, wrong schema, and release-phase incomplete coverage — all must pause (fail-closed) with zero sends. 59 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): route destination safety, rejection semantics, data provenance, protocol correctness P1 (Responses default): Remove the OpenRouter fallback for the Responses route. An unconfigured PC_GATEWAY_UPSTREAM_URL_RESPONSES now returns 501 with a configuration hint instead of silently selecting a third-party destination the operator never approved. P2 (GateRejected): Distinguish a reachable Core's HTTP refusal (401/403/ 422) from transport unavailability. New GateError::GateRejected variant pauses rather than triggering degradation; both HTTP and UDS clients classify non-2xx as rejections. UDS retryable() explicitly rejects. P2 (sediment provenance): The transcript's user field now uses the pristine (pre-injection) user text, preventing recalled memory from being captured as new user evidence. P2 (RFC3339 fractions): Short fractional seconds are right-padded to scale correctly (.5 = 500ms, .46 = 460ms) instead of being truncated, eliminating premature deadline expiry of up to 495ms. P2 (SSE CRLF): Event separation now matches the complete blank-line delimiter (\r\n\r\n or \n\n) and consumes its actual length; CRLF-terminated events no longer fragment. P2 (chunk overflow): Chunk sizes are validated against the body budget before any arithmetic, preventing overflow panics from peer-controlled values. 7 new probe tests: gate-rejected-pause, sediment-pristine-user-text, fraction-scaling, fraction-equivalence, SSE CRLF/LF/mixed. 66 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): preserve token limits, validate contract versions, configurable assembly, Responses continuation ID P2 (max_tokens default): OutputLimits.max_output_tokens is now Option<u32>; when the client does not supply a token limit the field is omitted from the wire body (provider default applies) instead of silently truncating at 1024. P2 (contract validation): resolve_degradation now verifies that the Core's capabilities include check_versions containing powercontext.context-check.v1 and payload_versions containing powercontext.model-json-payload.v1; missing contract members disable degradation and log an error explaining the required merge order. P2 (assembly limit): The memory-only assembly entry limit is now configurable via PC_GATEWAY_ASSEMBLY_LIMIT (default 8, capped at 8) instead of hardcoded, preventing silent injection loss when the deployment's context_assembly_max_entries is lower. P2 (previous_response_id): The Responses DTO captures and passes through the continuation ID to the Responses renderer; Core sees and can reject unsupported history references instead of the gateway silently converting a continuation into a fresh request. Tests: 5 new probe tests (omitted/explicit token limits for Chat and Responses, previous_response_id wire-body reach). The Responses renderer now emits previous_response_id alongside store. 70 tests; clippy zero warnings. * fix(gateway): preserve typed user text in recall query and transcript last_user_text() only matched a single plain-string content and returned None for the valid typed-parts form [{"type":"text","text":"..."}], causing two silent data losses: the prepare recall query was empty (injection skipped entirely) and the sediment transcript's user field was an empty string despite the upstream receiving the original text. The helper now extracts and joins text fields from typed content parts. Probe test: typed_content_parts_preserved_in_query_and_transcript. 71 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): cumulative UDS budget, earliest SSE separator, token-limit field name UDS (cumulative budget): read_with_timeout enforces the remaining budget before each read (buf.len() >= MAX → reject) and caps the chunk size to the remaining allowance, so a peer sending many under-limit chunks cannot buffer past 8 MiB total. Content-Length declarations exceeding the budget are rejected before read_more. SSE (mixed line endings): find_blank_line_with_len now searches for both CRLF (\r\n\r\n) and LF (\n\n) separators simultaneously and returns the earliest by position, so a valid LF event followed by a CRLF event is correctly split regardless of transport chunking. Chat (token-limit field name): CanonicalRequest carries token_limit_field to remember whether the caller supplied max_completion_tokens or max_tokens; the chat renderer emits the original field name. OpenAI o-series only accepts max_completion_tokens, so the previous rewrite to max_tokens broke those models. Tests: mixed_lf_then_crlf_events_not_merged, max_completion_tokens_field_name_preserved, max_tokens_field_name_preserved. 74 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * ci(gateway): align gateway workflow with the master orchestrator pattern Reworks gateway.yml from a path-triggered workflow to the workflow_call pattern used by desktop.yml: a changes job resolves suite selection when invoked standalone; the check job conditionally runs based on the gateway suite from either inputs.suites or the changes output. Adds a gateway-validation job to master.yml that calls the workflow, passes suites, and is included in the result gate. The [checks] entry now references gateway-validation. This fixes two test_ci_pipeline.py failures: - test_selected_scopes_have_a_complete_terminal_result_contract - test_automatic_host_workflows_share_one_scope_and_admission_gate All 123 CI-related tests, 351 Python tests, license check, and 74 Rust tests pass. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): workflow permissions, typed-content injection, UDS budget boundary - Gateway workflow now grants actions: read alongside contents: read; the nested ci-changes.yml workflow requires actions: read for standalone (workflow_dispatch) execution - enrich() now handles typed content parts when injecting memory: extracts text fields from [{"type":"text","text":"..."}] parts instead of returning early, so recalled context is injected into typed-part messages as well as plain-string messages - UDS cumulative budget allows a body that is exactly MAX_BODY_BYTES (8 MiB); the pre-read check uses > not >= so a single chunk at the exact budget is accepted, while cumulative reads past the budget are rejected 74 Rust tests + 123 CI tests pass; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): token_limit_field conditionally set in Chat ingress; end-to-end verified The token_limit_field was inadvertently set to None in the Chat ingress path when a batch script added the field to all constructor sites — overwriting the intended conditional assignment. The renderer correctly uses the field name, but the ingress never populated it, so max_completion_tokens was still rewritten to max_tokens in the actual HTTP path (unit tests passed because they tested the renderer directly, bypassing the ingress). End-to-end verified: client sends max_completion_tokens:16 → upstream receives max_completion_tokens:16 with no max_tokens field present. 74 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): preserve non-text parts when injecting into typed content Injecting recalled context into a typed-parts message previously replaced the entire content array with a single string assembled from .text values, silently discarding image_url and other non-text parts. A user message containing text plus an image would lose the image before Core admission, so Core's content_unsupported verdict was never reached and the request was incorrectly allowed. The enrich() function now modifies only the text parts in place (prepending the memory block to each text value) and passes all non-text parts through unchanged, so unsupported content remains visible to Core. Probe test: mixed_content_preserves_non_text_parts_during_injection (text + image_url → upstream receives both memory-injected text AND the original image part). 75 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): single-injection for multi-text parts, typed pointer, UDS cumulative budget Typed parts injection (enrich): - Only the first text part receives the memory block; subsequent text parts are unchanged (no duplicate injection that would inflate payload and desync audit records) - prepared_contexts pointer for typed parts now uses the /messages/ {i}/content/{j}/text form (the /messages/{i}/content form is only valid for plain-string content) UDS cumulative budget (read_chunked): - Budget check moved from read_with_timeout to read_chunked where the decoded body size is known: out.len() + size > MAX_BODY_BYTES is checked before reading/appending, not after buffering 64 MiB - read_with_timeout no longer clamps chunk_size to remaining budget (which caused zero-length reads at exactly 8 MiB); it reads what the caller requests, and the caller enforces the limit Tests: multi_text_parts_memory_injected_only_once, typed_parts_pointer_targets_text_field. 77 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): cumulative UDS chunk budget before reading chunk data The cumulative budget check was inadvertently lost during a batch edit. It is now placed between the chunk-size parse and the chunk-data read loop: out.len() + size > MAX_BODY_BYTES is evaluated before any chunk bytes are read from the socket, so 16 x 4 MiB chunks are rejected at chunk 3 without buffering 64 MiB. The single-chunk size guard and Content-Length precheck remain in place. 77 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> * fix(gateway): capture and pass through the Responses conversation parameter The Responses API has a conversation parameter (separate from previous_response_id) that identifies the conversation. Like previous_response_id, it was silently dropped by the DTO because serde ignores unknown fields. The DTO now captures it, the CanonicalRequest carries it, and the Responses renderer emits it to the wire body — Core sees the conversation reference and can withhold with content_field_unsupported if the target does not support it. Probe test: conversation_field_reaches_wire_body. 78 tests; clippy zero warnings. Signed-off-by: ztelur <ztelur@users.noreply.github.com> --------- Signed-off-by: ztelur <ztelur@users.noreply.github.com> Co-authored-by: ztelur <ztelur@users.noreply.github.com>
Replacement pull requests
This combined PR is superseded by five separately reviewable PRs. The implementation and its existing review fixes are preserved in the appropriate feature branches, rebased onto master
4af95ce2e7e5406fa974f1dcd6532fc0b157a406.POST /v1/context/check, generated contracts, and host adaptersThe dependent PRs remain drafts with feature-branch bases to keep their diffs focused. Once each parent lands, rebase its children onto master and retarget them before marking them ready. The UDS change can merge independently.
Validation and compatibility
Each branch passed its focused tests and
make check. The assembled branches merge without conflicts and passedmake checkplus all 56 contract tests. Final documentation amendments restore cache/cost boundaries without changing the tested runtime tree. Each replacement PR records its own test scope and limitations; newly triggered GitHub checks must be evaluated separately.The six addressed review issues are retained, including provider storage defaults, opaque references/documents, refusal recovery through Memory flush/restart, usage compatibility, and SSE reasoning inspection. No database table or migration is introduced. The new HTTP endpoint and opt-in preparation envelope are isolated in #2313.
One redundant upgrade-test environment assignment was omitted because current master's fixture already clears the same variable; the three affected regression cases passed without it. Historical Desktop qualification records retain their original source and wheel identities. No new live provider quality/cost or external database acceptance is claimed.
AI usage statement
OpenAI Codex assisted with implementation review, fixes, feature separation, conflict resolution, documentation, validation, and PR preparation.