Sunburst Model Verification
GOO-370 · Verification

Sunburst now calls OpenAI directly — generate and edit, on both surfaces.

GPT Image 2.5 Sunburst is wired to OpenAI's Images API as a new first-class openai provider. Four cases were driven through the running system — generate and edit, in Galleria and on the workflow canvas — and verified against the database, the service logs, and the bytes actually stored.

Branch ziann/goo-370-feature-add-sunburst-model Identifier gpt-image-2.5-sunburst Provider openai

What was tested

Galleria and the workflow canvas are separate code paths to the same backend, with their own configs and defaults, so each was exercised independently. Every run was confirmed three ways: the outgoing request body, the database row reaching a terminal state, and the provider path in the service log.

CaseSurfaceProvider path calledResult
GenerateGalleria /v1/images/generationspass · 11.6s
EditGalleria /v1/images/editspass · 12.6s
GenerateWorkflow node /v1/images/generationspass
EditWorkflow node /v1/images/editspass
GPT Image 2 regressionBoth fal.run/openai/gpt-image-2unaffected

The parameter surface

Every value below was confirmed by sending it to the live API, not read from documentation. size takes literal pixel dimensions rather than preset names, which is what lets the interface quote real output dimensions on the generate button.

Wire keyAccepted valuesExposed in UI
sizeauto, 1024x1024, 1536x1024, 1024x1536, custom WxH4 options
qualityauto, low, medium, high, xhigh, maxall 6
backgroundauto, transparent, opaqueall 3
output_formatpng, jpeg, webpall 3
ninteger1–4
POST /api/generations → 201        (from a real Galleria run)
"config":{"size":"1024x1024","quality":"low","background":"opaque","output_format":"png","n":1}
The Galleria panel with the Size dropdown open, showing Auto, 1:1 Square, 3:2 Landscape and 2:3 Portrait.
Galleria · size options Four options, all real OpenAI dimensions. Note the button reads Generate (15 credits · 1536x1024) — it can quote actual output dimensions because size carries them directly.

The edit path

This was the one genuinely novel piece of code. Every other provider client in the repository posts JSON; OpenAI's edit endpoint takes multipart/form-data with binary file parts, so the reference images must be fetched to bytes and assembled into a form.

[OpenAI Image] model=gpt-image-2.5-sunburst edit=true images_in=1
[OpenAI] POST /images/edits model=gpt-image-2.5-sunburst images=1
A golden retriever sitting, facing the camera, on a soft gradient background.
Source · 1536x1024, quality high, background transparent Generated first, stored to GCS, then fed back in as the reference. The stored file is RGBA, so the transparent background survives all the way to storage.
The same golden retriever, now wearing a red patterned bandana, otherwise unchanged.
After · "put a red bandana around the dog's neck, keep everything else identical" Something was added, not recoloured — a far harder edit than tinting a solid object. The fur, tongue, paws, pose and even the background gradient are untouched. This file is RGB, because background is one of the parameters OpenAI rejects on edits, so the service drops it.
Screen recording · Galleria edit, start to finish Model selection, the Size dropdown, attaching a reference, and the finished edit appearing in the feed. Recorded at 1600×1000 — at 1280 the results feed overlaps the settings column and intercepts clicks on the Size control, which broke the run outright.

Transparency, counted rather than assumed

background: transparent is the capability most likely to look right and be wrong, because an alpha channel can exist while being opaque everywhere. So the stored asset was decoded and its pixels counted, rather than trusting file to report RGBA.

A red maple leaf with a fully transparent background.
Galleria · background transparent, 1536x1024 Requested from the interface, with no reference image. The wire carried "background":"transparent" and the stored file is RGBA, all four corners at alpha 0.
fully clear (a<16)     1,200,953  of 1,572,864   76.4%
fully opaque (a>239)     364,119
partial (anti-aliased)     7,792
corner alpha          [0, 0, 0, 0]

Base64, decoded and checked

Unlike every other provider here, OpenAI returns no URL — only base64. That makes the decode the highest-risk part of the change, and a failure would be close to invisible: a wrong decode stores base64 text labelled image/png, and the response, the byte count and the metadata all still look healthy.

$ curl <generation_assets.public_url> -o stored.png
$ file stored.png
stored.png: PNG image data, 1024 x 1024, 8-bit/color RGB, non-interlaced
$ xxd -l 8 stored.png
00000000: 8950 4e47 0d0a 1a0a   .PNG....

Both surfaces, and the neighbour that had to keep working

The workflow canvas with two Sunburst nodes and a GPT Image 2 node, settings panels visible.
Workflow canvas · Sunburst and GPT Image 2 side by side Sunburst nodes show four controls including Background, which no other image model has; the neighbouring GPT Image 2 node shows three and keeps its own 1:1 HD preset, unaffected. The Size control seeds correctly now; this shot predates that fix.

Open findings

Two things fixed after first being written up here

Node size default

A fresh workflow node used to render an empty Size control. It now seeds correctly.

The node factory hardcoded imageSize: "square_hd" for every model. That is a FAL preset name, so it matched no OpenAI option and the dropdown had nothing to display — output was still correct, because the execute-time guard clamped it, but the control looked broken until touched.

It now reads each model's own declared default instead of a fixed string. All nine branches were enumerated to confirm the change is limited to one: every model that declares no size setting still falls back to square_hd, and gpt-image-2 keeps that value explicitly. Only Sunburst's seeded value differs, and its wire value is unchanged — it was already arriving at 1024x1024, just via the clamp rather than the default.

Picker position

Sunburst sorts last among active models, at sequence 25.

It initially shared sequence 5 with gpt-image-2, which made the order between them depend on a name tiebreak rather than being specified. Moving it to 25 — past the highest active model at 24, before the legacy block at 90 — makes the position explicit and ties with nothing, while still moving no other row.

Not covered

Stated plainly, because a verification report that implies more coverage than it has is worth less than one that names its own edges.