Skip to content

feat(api): fail-closed image_url rasters and persist message_image_unit - #676

Closed
cursor[bot] wants to merge 22 commits into
mainfrom
cursor/bc-401abd36-87a7-49f7-82f7-08903e63b6fb-fa81
Closed

feat(api): fail-closed image_url rasters and persist message_image_unit#676
cursor[bot] wants to merge 22 commits into
mainfrom
cursor/bc-401abd36-87a7-49f7-82f7-08903e63b6fb-fa81

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Summary

Unique slice on top of #637 5358632: invoice/receipt photos in OpenAI image_url parts no longer bill a vision hop when the payload is HTML, javascript:, or a truncated PNG. A real 1×1 PNG stamp is accepted. Sqlite persists a 3NF message_image_unit with the original part_index beside neighboring invoice text so operators can reopen the figure after restart.

Buyer next action: send a complete PNG/JPEG data URI or https://…/receipt.png next to the invoice line, then call list_message_image_units and open the unit whose neighbor_text contains the invoice id.

Serve-runtime landing remains #637. Do not treat this PR as a second serve-runtime path vehicle. Do not merge #645/#656 in parallel for that slice. Do not fold #643/#652 meaning-unit embeddings or #631/#650 reasoning-effort into this tip.

Test plan

  • python3 tests/test_message_image_units.py (HTML/JS/truncated PNG fail closed with part_index=1; real stamp 200; sqlite restart finds INV-4419 at part_index 1)
  • python3 tests/test_multimodal_message_content_http_honesty.py
  • python3 tests/test_multimodal_content_parts_shape_http_honesty.py
  • python3 tests/test_persistence.py test_serve_runtime_kv.py test_conventions.py test_api_contract.py test_self_check.py
  • CI Full unit + Semgrep

Docs

  • docs/papers/README.md APA 7th: Xu et al. (2020) LayoutLM; Masinter (1998) RFC 2397; Boutell (1997) RFC 2083.
  • docs/library_research.md Ponytail: stdlib base64/urlparse; skip Pillow and live OCR in this slice.
  • docs/database_design.sql message_image_unit (3NF, two-word names, retention).

Independent non-author APPROVE + Full unit/Semgrep still required.

Open in Web View Automation 

seonghobae and others added 22 commits August 16, 2026 21:18
…closed otherwise

Chat history: message-level audio and legacy function_call are null/empty omit
no-ops; non-empty fail closed with named errors (including tools passthrough).
Tip substrate from #577 assistant refusal/annotations honesty. Local full unit:
940 passed.
…ed otherwise

OpenAI fine-tune style message weight is not applied on this gateway.
Accept null/0/1 as honest no-ops; reject other types and values with
invalid_message_weight. Tip substrate from #578. Local full unit: 943 passed.
…ion role

Reject unsupported message keys with named unknown_message_fields (not silent
strip or tools-passthrough smuggle). Reject legacy function role with
invalid_message_role migration to tool. Tip substrate from #579. Local full
unit: 947 passed.
OpenAI partial-assistant prefix flag is not applied on this gateway.
null/false are honest no-ops; true and non-booleans fail closed with
invalid_message_prefix. Tip substrate from #580. Local full unit: 950 passed.
…therwise

Named invalid_max_tool_calls on /v1/chat/completions instead of opaque
unknown_fields. Aligns with Responses max_tool_calls honesty; gateway has
no multi-step tool loop.
…losed otherwise

Legacy /v1/completions treated max_tool_calls as unknown_fields. Accept the
key for named invalid_max_tool_calls (null/empty/whitespace omit-equivalent),
matching chat/Responses honesty so SDKs get a clear migration path.
SDK clients often send include_usage/include_obfuscation as JSON null.
Drop null flag values before validation so null (and null+false mixes)
match omit / all-false no-ops on chat, Completions, and Responses.
True flags remain fail-closed with invalid_stream_options.
…age honesty

Null include_usage/include_obfuscation stay omit-equivalent, but unknown
stream_options keys no longer become no-ops just because their value is
null. Weight, prefix, refusal, annotations, developer role, empty
user/system content, and participant name now use the same named errors
on the tools passthrough path as on orchestration.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist stream, required model, stream_options, and temperature/top_p range
checks before proxy_completion so a tools or response_format body cannot
return a billed JSON completion when the SDK asked for SSE, or silently
pick a pool model when model is omitted.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Call the orchestration message, max_tokens, attribution, and routing
validators before proxy_completion so a tools or response_format body
cannot bill a completion with no prompt, crash on a non-object entry,
or silently drop unknown spend/routing keys.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Passthrough has no batch job plane. Reject routing.channel=batch and
latency_tolerant=true before proxy_completion so a tool-calling body
cannot bill a silent sync completion.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist the orchestration request-knob validators before proxy_completion
so a tools or response_format body cannot bill a completion that silently
drops seed, stop, n>1, logprobs, logit_bias, or out-of-range penalties.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
stream_chat defaulted to 0.2 even after the HTTP path wrote
default_temperature from the request. A streamed invoice summary at
temperature=0.8 no longer silently falls back to 0.2.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
… proxy

SDK optional defaults serialize omitted fields as JSON null. Accepting those
keys without popping them is not omit-equivalent: proxy_completion forwards
the body and several providers reject a null JSON Schema object.

Pop the keys in place, keep non-null wrong types on invalid_tools, and assert
omit-real via mock echo on chat and Responses.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Stop reading CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS in
ModelClient._validate_provider. The allowlist lives in
provider_egress.allowed_provider_hosts; env is bootstrap transport
via seed_provider_egress_from_environ only.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Prove a second seed() after an env edit keeps the first KV set, that
_validate_provider ignores env-only allowlists, and that a detached
get_config_store() write is not the request-time source. Hold the
runtime lock across seed read+write and fuzz the host CSV parser.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Irreversible email [REDACTED] on opt-in traces made invoice and HR
tickets unworkable. Credential shapes stay redacted. Access control
plus audit is the PII control (NIST SP 800-122; SP 800-53 AC-3/AU-2).

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
CONTEXTUAL_ORCHESTRATOR_TOKEN and the split admin/inference vars are
bootstrap transport via seed_server_auth_from_environ. Explicit
--auth-token still wins. A later env edit does not change who can call
the API.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
…me KV

CONTEXTUAL_ORCHESTRATOR_STATE_DB / _AGENTS_DB / _CLEARFOLIO_URL /
_PROVIDER_CA_BUNDLE are bootstrap transport via
seed_serve_runtime_from_environ. Explicit CLI flags still win. A later
env edit does not retarget persistence, the document viewer, or provider TLS.

Buyer next action: pass --state-db / --agents-db / --clearfolio-url /
--provider-ca-bundle, or start once with the matching env vars so the
serve_runtime KV can copy them, then open the KV sqlite path or
Clearfolio URL.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
HTML, javascript, and truncated PNG data URIs no longer bill a vision hop.
A real invoice-stamp PNG is accepted. Sqlite keeps part_index beside the
neighbor invoice line so operators can reopen the figure after restart.

Buyer next action: send a complete PNG/JPEG data URI or https receipt URL
next to the invoice line, then call list_message_image_units.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 16:50
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 17:06
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Author

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@seonghobae
seonghobae enabled auto-merge (squash) August 16, 2026 17:06
@seonghobae seonghobae closed this Aug 16, 2026
auto-merge was automatically disabled August 16, 2026 18:22

Pull request was closed

@seonghobae

Copy link
Copy Markdown
Contributor

Superseded by tip #691 (cumulative honesty substrate ≥ #689). Closing to free Full unit + Semgrep runners.

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.

2 participants