img2img through the jobs queue — restyle from flexsiebels (#11 follow-up) #13

Open
opened 2026-06-06 14:06:43 +00:00 by mAi · 2 comments
Collaborator

Why

#11 added img2img/restyle to the Backend contract + the imagen CLI. But flexsiebels does not use the CLI — it talks to ImaGen purely through the imagen.jobs queue (write path from #8): it inserts a job row via Supabase (website/src/routes/api/admin/imagine/jobs/+server.ts.from('jobs').insert(...)), the imagen worker on mRiver claims it, generates, writes imagen.images + Storage, sets image_id.

That queue has no img2img columns, and worker.Job / the pipeline have no init-image path. So restyle is unreachable from flexsiebels (m's main frontend) until the queue learns it. This issue plumbs img2img through the queue so flexsiebels (and any other queue producer) can enqueue a restyle.

Schema (migration — ImaGen owns the imagen schema)

All additions nullable → backward-compatible with existing txt2img inserts (flexsiebels' current inserts keep working untouched).

  • imagen.jobs:
    • init_image_id uuid NULL REFERENCES imagen.images(id) — the source image to condition on (the natural flexsiebels case: "restyle this image I'm looking at", which is already an imagen.images row).
    • strength real NULL — transformation amount in [0,1]; maps to backend.Request.Strength. Default at the producer; pipeline treats NULL as the backend/CLI default (0.6).
  • imagen.images:
    • source_image_id uuid NULL REFERENCES imagen.images(id) — lineage: a restyle result points at its source. Enables #6's "fork from this image" and a viewer before/after.

Deliver the migration as a SQL file in the repo's migration location (match how #7 created the imagen schema). The head applies it to Supabase at merge time (irreversible infra action) via apply_migration — do not apply it yourself; commit the SQL + note it in the done report.

Worker + pipeline

  • internal/worker/worker.go Job struct: add InitImageID string, Strength float64. Zero values = txt2img (same convention as today).
  • The pgx Queue claim query (in cmd/imagen): select the two new columns into Job.
  • Pipeline Run (the implementation behind the Pipeline interface): when InitImageID != "":
    1. Look up the source imagen.images row; fetch its bytes from Storage via storage_path (extend internal/cloud with a download — it already has the Storage client + bucket from #7).
    2. Sniff MIME, build backend.Request{InitImage: &backend.ImageInput{Data, MimeType, Filename}, Strength} (Strength = job.Strength, or the 0.6 default when 0).
    3. Dispatch as today (the comfyui backend's img2img path from #11 handles it).
    4. On cloud-sync of the result, set the new row's source_image_id = init_image_id and record mode=img2img + strength + source_image_id in the sidecar jsonb (the sidecar writer already serialises Result.Metadata).
  • Honest failure: if the job's backend is not ImageInputCapable / SupportsImageInput() is false, MarkFailed with a clear error ("backend X does not support image input") — never silently fall back to txt2img. (#11 already gives the sentinel ErrImageInputUnsupported.)

Tests (house rules: next to packages, no network)

  • worker_test.go: Job round-trips InitImageID/Strength; pipeline img2img branch with a fake Storage fetch + mock backend; capability-failure path → MarkFailed with the right message; txt2img path unchanged when InitImageID empty.
  • Queue claim test selects the new columns (mock pgx as existing tests do).

Out of scope

  • The flexsiebels UI (paul / flexsiebels head owns it — briefed separately).
  • ControlNet / Redux (ImaGen #11 phase 2/3), Replicate img2img.

Refs

  • #11 (Backend contract + CLI img2img), #8 (jobs queue + worker), #7 (cloud sync + imagen.images), #6 (viewer fork-from-image), #12 (live smoke)
  • Producer side: flexsiebels website/src/routes/api/admin/imagine/jobs/+server.ts, lib/server/imagen.ts
## Why #11 added img2img/restyle to the Backend contract + the `imagen` CLI. But **flexsiebels does not use the CLI** — it talks to ImaGen purely through the `imagen.jobs` queue (write path from #8): it inserts a job row via Supabase (`website/src/routes/api/admin/imagine/jobs/+server.ts` → `.from('jobs').insert(...)`), the `imagen worker` on mRiver claims it, generates, writes `imagen.images` + Storage, sets `image_id`. That queue has **no img2img columns**, and `worker.Job` / the pipeline have no init-image path. So restyle is unreachable from flexsiebels (m's main frontend) until the queue learns it. This issue plumbs img2img through the queue so flexsiebels (and any other queue producer) can enqueue a restyle. ## Schema (migration — ImaGen owns the `imagen` schema) All additions **nullable** → backward-compatible with existing txt2img inserts (flexsiebels' current inserts keep working untouched). - `imagen.jobs`: - `init_image_id uuid NULL REFERENCES imagen.images(id)` — the source image to condition on (the natural flexsiebels case: "restyle this image I'm looking at", which is already an `imagen.images` row). - `strength real NULL` — transformation amount in [0,1]; maps to `backend.Request.Strength`. Default at the producer; pipeline treats NULL as the backend/CLI default (0.6). - `imagen.images`: - `source_image_id uuid NULL REFERENCES imagen.images(id)` — lineage: a restyle result points at its source. Enables #6's "fork from this image" and a viewer before/after. Deliver the migration as a SQL file in the repo's migration location (match how #7 created the `imagen` schema). **The head applies it to Supabase at merge time** (irreversible infra action) via `apply_migration` — do not apply it yourself; commit the SQL + note it in the done report. ## Worker + pipeline - `internal/worker/worker.go` `Job` struct: add `InitImageID string`, `Strength float64`. Zero values = txt2img (same convention as today). - The pgx `Queue` claim query (in `cmd/imagen`): select the two new columns into `Job`. - Pipeline `Run` (the implementation behind the `Pipeline` interface): when `InitImageID != ""`: 1. Look up the source `imagen.images` row; fetch its bytes from Storage via `storage_path` (extend `internal/cloud` with a download — it already has the Storage client + bucket from #7). 2. Sniff MIME, build `backend.Request{InitImage: &backend.ImageInput{Data, MimeType, Filename}, Strength}` (Strength = job.Strength, or the 0.6 default when 0). 3. Dispatch as today (the comfyui backend's img2img path from #11 handles it). 4. On cloud-sync of the result, set the new row's `source_image_id = init_image_id` and record `mode=img2img` + `strength` + `source_image_id` in the sidecar jsonb (the sidecar writer already serialises Result.Metadata). - **Honest failure**: if the job's backend is not `ImageInputCapable` / `SupportsImageInput()` is false, `MarkFailed` with a clear error ("backend X does not support image input") — never silently fall back to txt2img. (#11 already gives the sentinel `ErrImageInputUnsupported`.) ## Tests (house rules: next to packages, no network) - `worker_test.go`: `Job` round-trips `InitImageID`/`Strength`; pipeline img2img branch with a fake Storage fetch + mock backend; capability-failure path → `MarkFailed` with the right message; txt2img path unchanged when `InitImageID` empty. - Queue claim test selects the new columns (mock pgx as existing tests do). ## Out of scope - The flexsiebels UI (paul / flexsiebels head owns it — briefed separately). - ControlNet / Redux (ImaGen #11 phase 2/3), Replicate img2img. ## Refs - #11 (Backend contract + CLI img2img), #8 (jobs queue + worker), #7 (cloud sync + `imagen.images`), #6 (viewer fork-from-image), #12 (live smoke) - Producer side: flexsiebels `website/src/routes/api/admin/imagine/jobs/+server.ts`, `lib/server/imagen.ts`
Author
Collaborator

shift-1 (hephaestus) — #13 implemented end-to-end

Commit 2904ac2 on mai/hephaestus/img2img-through-the-jobs. go build ./..., go vet ./..., go test ./... all clean.

Migration (committed UNAPPLIED — head applies at merge)

migrations/imagen_img2img_lineage.sql:

  • imagen.jobs += init_image_id uuid NULL REFERENCES imagen.images(id) ON DELETE SET NULL, strength real NULL CHECK (0..1)
  • imagen.images += source_image_id uuid NULL REFERENCES imagen.images(id) ON DELETE SET NULL + partial lineage index imagen_images_source_idx
  • All additions NULLable → existing txt2img inserts keep working untouched. Style mirrors imagen_jobs_init / imagen_series_init.
  • Action for head: apply via Supabase apply_migration at merge time.

Worker + pipeline

  • worker.Job += InitImageID string, Strength float64 (zero values = txt2img).
  • pgx claim query selects both new columns (COALESCE(init_image_id::text,''), COALESCE(strength,0)).
  • internal/cloud.FetchImage(imageID): resolves storage_path via PostgREST (imagen profile) then downloads the object from the single imagen-generated bucket (per cronus' coordinated decision — no bucket param; user-uploaded/URL sources land under inputs/... in the same bucket). MIME sniffed when the Storage header is missing/octet-stream. SyncRequest.SourceImageID written to the result row.
  • Pipeline Run img2img branch: capability check → fetch source bytes → Request.InitImage+Strength (NULL strength → 0.6 default, matching #11's CLI/backend default) → dispatch (comfyui img2img path from #11) → stamps source_image_id+mode+strength onto Result.Metadata (lands in disk sidecar + imagen.images.sidecar jsonb) and sets source_image_id column.
  • Honest failure: a backend that isn't ImageInputCapable / reports SupportsImageInput()==false → hard MarkFailed ("backend X does not support image input"), never a silent txt2img fallback.

Tests (next to packages, no network)

  • internal/worker/worker_test.go: Job round-trips InitImageID/Strength; txt2img path leaves them zero.
  • cmd/imagen/worker_img2img_test.go: capability-failure → failed Outcome with the right message; resolveInitImage fetch success + error; full Run builds Request.InitImage+default strength and records lineage metadata; explicit strength preserved.
  • internal/cloud/cloud_test.go: FetchImage happy path (bucket-relative URL + imagen profile header), MIME-sniff fallback, not-found; SourceImageID written/omitted in the insert body.
  • cmd/imagen/worker_integration_test.go: env-guarded real-pgx test that the claim query scans init_image_id+strength into worker.Job.

Out of scope (untouched, per issue)

flexsiebels UI (paul), ControlNet/Redux, Replicate img2img.

## shift-1 (hephaestus) — #13 implemented end-to-end Commit `2904ac2` on `mai/hephaestus/img2img-through-the-jobs`. `go build ./...`, `go vet ./...`, `go test ./...` all clean. ### Migration (committed UNAPPLIED — head applies at merge) `migrations/imagen_img2img_lineage.sql`: - `imagen.jobs` += `init_image_id uuid NULL REFERENCES imagen.images(id) ON DELETE SET NULL`, `strength real NULL CHECK (0..1)` - `imagen.images` += `source_image_id uuid NULL REFERENCES imagen.images(id) ON DELETE SET NULL` + partial lineage index `imagen_images_source_idx` - All additions NULLable → existing txt2img inserts keep working untouched. Style mirrors `imagen_jobs_init` / `imagen_series_init`. - **Action for head:** apply via Supabase `apply_migration` at merge time. ### Worker + pipeline - `worker.Job` += `InitImageID string`, `Strength float64` (zero values = txt2img). - pgx claim query selects both new columns (`COALESCE(init_image_id::text,'')`, `COALESCE(strength,0)`). - `internal/cloud.FetchImage(imageID)`: resolves `storage_path` via PostgREST (imagen profile) then downloads the object from the **single `imagen-generated` bucket** (per cronus' coordinated decision — no bucket param; user-uploaded/URL sources land under `inputs/...` in the same bucket). MIME sniffed when the Storage header is missing/octet-stream. `SyncRequest.SourceImageID` written to the result row. - Pipeline `Run` img2img branch: capability check → fetch source bytes → `Request.InitImage`+`Strength` (NULL strength → 0.6 default, matching #11's CLI/backend default) → dispatch (comfyui img2img path from #11) → stamps `source_image_id`+`mode`+`strength` onto `Result.Metadata` (lands in disk sidecar + `imagen.images.sidecar` jsonb) and sets `source_image_id` column. - **Honest failure:** a backend that isn't `ImageInputCapable` / reports `SupportsImageInput()==false` → hard `MarkFailed` ("backend X does not support image input"), never a silent txt2img fallback. ### Tests (next to packages, no network) - `internal/worker/worker_test.go`: Job round-trips `InitImageID`/`Strength`; txt2img path leaves them zero. - `cmd/imagen/worker_img2img_test.go`: capability-failure → failed Outcome with the right message; `resolveInitImage` fetch success + error; full Run builds `Request.InitImage`+default strength and records lineage metadata; explicit strength preserved. - `internal/cloud/cloud_test.go`: `FetchImage` happy path (bucket-relative URL + imagen profile header), MIME-sniff fallback, not-found; `SourceImageID` written/omitted in the insert body. - `cmd/imagen/worker_integration_test.go`: env-guarded real-pgx test that the claim query scans `init_image_id`+`strength` into `worker.Job`. ### Out of scope (untouched, per issue) flexsiebels UI (paul), ControlNet/Redux, Replicate img2img.
Author
Collaborator

Merged to main (933b2e2) and migration applied to Supabase.

What landed:

  • migrations/imagen_img2img_lineage.sql applied — verified live: imagen.jobs.init_image_id (uuid), imagen.jobs.strength (real, CHECK 0..1), imagen.images.source_image_id (uuid) + partial index. All nullable, so existing txt2img producers are unaffected.
  • worker.Job carries InitImageID/Strength; pgx claim query selects them.
  • internal/cloud.FetchImage resolves storage_path by image id and downloads from the single imagen-generated bucket.
  • Pipeline img2img branch: hard fail (job marked failed) when the backend is not ImageInputCapable — never a silent text-to-image fallback. Strength NULL → 0.6. Records source_image_id + mode + strength lineage on the result row + sidecar.
  • Tests across worker / pipeline img2img / cloud fetch. go build/vet/test ./... clean.

Remaining to go live: the imagen-worker on mRiver must be redeployed with this binary (the old process keeps running and safely ignores the new columns, but won't process restyle jobs until restarted). After that + flexsiebels' ENABLE_RESTYLE flag, restyle is live end-to-end.

Contract is now open for the flexsiebels producer (init_image_id + strength on the jobs insert). Implemented by hephaestus.

**Merged to main** (`933b2e2`) and **migration applied** to Supabase. What landed: - `migrations/imagen_img2img_lineage.sql` applied — verified live: `imagen.jobs.init_image_id` (uuid), `imagen.jobs.strength` (real, CHECK 0..1), `imagen.images.source_image_id` (uuid) + partial index. All nullable, so existing txt2img producers are unaffected. - `worker.Job` carries `InitImageID`/`Strength`; pgx claim query selects them. - `internal/cloud.FetchImage` resolves `storage_path` by image id and downloads from the single `imagen-generated` bucket. - Pipeline img2img branch: **hard fail** (job marked failed) when the backend is not `ImageInputCapable` — never a silent text-to-image fallback. Strength NULL → 0.6. Records `source_image_id` + `mode` + `strength` lineage on the result row + sidecar. - Tests across worker / pipeline img2img / cloud fetch. `go build`/`vet`/`test ./...` clean. **Remaining to go live:** the `imagen-worker` on mRiver must be redeployed with this binary (the old process keeps running and safely ignores the new columns, but won't process restyle jobs until restarted). After that + flexsiebels' `ENABLE_RESTYLE` flag, restyle is live end-to-end. Contract is now open for the flexsiebels producer (init_image_id + strength on the jobs insert). Implemented by hephaestus.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/ImaGen#13
No description provided.