Art Studio: text+image concept, and fix image-upload submissions (#842) #846

Open
opened 2026-07-25 20:22:24 +00:00 by spikerj · 1 comment
Owner

Two things.

The bug (production). SubmitArtAssetValidator required SourceImageGridFsId to parse as an ObjectId. That was correct only while artifacts lived in GridFS. Since the MinIO cutover UploadAsync returns {assetId}__{guid:N}, and Storage__UseS3=true on the backend stack — so the API minted a key and then rejected its own submission with SourceImageGridFsId must be a valid ObjectId. Every image-upload art submission was failing. It stayed invisible because every test fixture used a GridFS-shaped id, so nothing ever fed the validator a key the production store actually produces.

The feature. ArtAssetSourceType.TextAndImage, which KEEPS the concept stage — the references condition a generated concept rather than replacing it (a pure ImageUpload still skips it, since there the image IS the concept). Methods declare an ImageAction; one without it is rejected by name rather than silently dispatched as text_to_image and ignoring the uploaded image.

References are a LIST end to end (ReferenceImageKeys), exposed as image_paths with image_path still the first — that is the FLUX.2 shape (up to ten references, conditioned by instruction, no denoise strength at all).

Two things. **The bug (production).** `SubmitArtAssetValidator` required `SourceImageGridFsId` to parse as an ObjectId. That was correct only while artifacts lived in GridFS. Since the MinIO cutover `UploadAsync` returns `{assetId}__{guid:N}`, and `Storage__UseS3=true` on the backend stack — so the API minted a key and then rejected its own submission with *SourceImageGridFsId must be a valid ObjectId*. **Every image-upload art submission was failing.** It stayed invisible because every test fixture used a GridFS-shaped id, so nothing ever fed the validator a key the production store actually produces. **The feature.** `ArtAssetSourceType.TextAndImage`, which KEEPS the concept stage — the references condition a generated concept rather than replacing it (a pure ImageUpload still skips it, since there the image IS the concept). Methods declare an `ImageAction`; one without it is rejected by name rather than silently dispatched as text_to_image and ignoring the uploaded image. References are a LIST end to end (`ReferenceImageKeys`), exposed as `image_paths` with `image_path` still the first — that is the FLUX.2 shape (up to ten references, conditioned by instruction, no denoise `strength` at all).
Author
Owner

Audited against origin/masterthe production bug is fixed, but one silent regression path remains. Staying open.

Fixed — the image-upload submission bug: SubmitArtAssetValidator.cs:43-47 now validates with IsValidStorageKey, applied to the source key at :81-89 and the list at :94-103. The remaining ObjectId.TryParse at :115 is for PhotoStack photograph ids and is correct there.

Also landed: ArtAssetSourceType.TextAndImage (ArtAsset.cs:614); ReferenceImageKeys as a list end-to-end (ArtAsset.cs:91-96, RequestArtAssetStageCommand.cs:294-295) surfaced as image_paths with image_path first for back-compat (ArtPipeStageOrchestrator.cs:1036-1043, :1138-1141) and consumed by the worker at sdxl_lightning.py:98-113; ImageAction declared per method (ArtStudioStageMethodOptions.cs:486-496) with rejection-by-name listing allowed keys (SubmitArtAssetCommandHandler.cs:391-397); TextAndImage keeps the concept stage while pure ImageUpload skips it (ArtAssetStagePlans.cs:52-62, test ArtAssetStagePlansTests.cs:94-103).

Remaining — restart silently reverts text+image to text. This is the most consequential of the epic's small gaps, because it's exactly the silent behaviour this ticket set out to eliminate:

  • Submit threads the source type: SubmitArtAssetCommandHandler.cs:399ToStageMethod(option, request.SourceType).
  • Restart does not: RestartFromStageCommandHandler.cs:390ToStageMethod(option), no second argument.
  • ActionFor (ArtStudioStageMethodOptions.cs:489-492) returns ImageAction only when sourceType == TextAndImage. So restarting a TextAndImage asset dispatches the text action and quietly ignores the reference images.

Also remaining — the SupportsSourceType guard has narrower reach than it appears. git grep -n "SupportsSourceType" origin/master -- SpikerSoft.Business SpikerSoft.Api returns exactly two hits, both inside the explicit selection loop (:391/:396). The preset-merge, failover and darkroom paths all use the source-type-unaware overload — SubmitArtAssetCommandHandler.cs:583, ArtStudioStageFailoverOptions.cs:173, DarkroomCommands.cs:224 — so a method that can't honour a reference image can still be selected through those routes.

Fix is small: pass the source type at RestartFromStageCommandHandler.cs:390, and decide whether the three unguarded paths should use the source-type-aware overload.

Audited against `origin/master` — **the production bug is fixed, but one silent regression path remains.** Staying open. **Fixed — the image-upload submission bug:** `SubmitArtAssetValidator.cs:43-47` now validates with `IsValidStorageKey`, applied to the source key at `:81-89` and the list at `:94-103`. The remaining `ObjectId.TryParse` at `:115` is for PhotoStack photograph ids and is correct there. **Also landed:** `ArtAssetSourceType.TextAndImage` (`ArtAsset.cs:614`); `ReferenceImageKeys` as a list end-to-end (`ArtAsset.cs:91-96`, `RequestArtAssetStageCommand.cs:294-295`) surfaced as `image_paths` with `image_path` first for back-compat (`ArtPipeStageOrchestrator.cs:1036-1043`, `:1138-1141`) and consumed by the worker at `sdxl_lightning.py:98-113`; `ImageAction` declared per method (`ArtStudioStageMethodOptions.cs:486-496`) with rejection-by-name listing allowed keys (`SubmitArtAssetCommandHandler.cs:391-397`); TextAndImage keeps the concept stage while pure ImageUpload skips it (`ArtAssetStagePlans.cs:52-62`, test `ArtAssetStagePlansTests.cs:94-103`). **Remaining — restart silently reverts text+image to text.** This is the most consequential of the epic's small gaps, because it's exactly the silent behaviour this ticket set out to eliminate: - Submit threads the source type: `SubmitArtAssetCommandHandler.cs:399` — `ToStageMethod(option, request.SourceType)`. - Restart does not: `RestartFromStageCommandHandler.cs:390` — `ToStageMethod(option)`, no second argument. - `ActionFor` (`ArtStudioStageMethodOptions.cs:489-492`) returns `ImageAction` **only** when `sourceType == TextAndImage`. So restarting a TextAndImage asset dispatches the *text* action and quietly ignores the reference images. **Also remaining — the `SupportsSourceType` guard has narrower reach than it appears.** `git grep -n "SupportsSourceType" origin/master -- SpikerSoft.Business SpikerSoft.Api` returns exactly two hits, both inside the explicit selection loop (`:391`/`:396`). The preset-merge, failover and darkroom paths all use the source-type-unaware overload — `SubmitArtAssetCommandHandler.cs:583`, `ArtStudioStageFailoverOptions.cs:173`, `DarkroomCommands.cs:224` — so a method that can't honour a reference image can still be selected through those routes. Fix is small: pass the source type at `RestartFromStageCommandHandler.cs:390`, and decide whether the three unguarded paths should use the source-type-aware overload.
Sign in to join this conversation.