Adds RequestArtAssetStageCommand.GenerationParamOverrides as a SEPARATE field from ArtAssetStageMethod.Params — those are static server config and caller input must not overwrite one by colliding on a key.
Enforcement is asymmetric on purpose: the API reports a bad value so the caller gets a 400 naming the real limit; the worker drops it with a warning instead, because a message that already passed validation should never fail a job that has burned GPU time.
prompt/negative_prompt are rejected, not clamped — both are moderated at submit and the restart path does not re-moderate new text, so accepting either would turn a knob into a laundering route for a blocked prompt on a platform serving minors.
Overrides are stage-scoped: carried on a same-stage failover republish (an alternate model must reproduce the same intent), omitted from the next-stage publish.
Also adds per-method capability descriptors to GET options so the composer renders the right control per model. Known limitation: those descriptors are DECLARED in the allow-list rather than read from the models' artpipe.json, because the manifests live on the worker's pipeline root and the API host has no copy. Keep the two in sync.
Adds `RequestArtAssetStageCommand.GenerationParamOverrides` as a SEPARATE field from `ArtAssetStageMethod.Params` — those are static server config and caller input must not overwrite one by colliding on a key.
Enforcement is asymmetric on purpose: the API reports a bad value so the caller gets a 400 naming the real limit; the worker drops it with a warning instead, because a message that already passed validation should never fail a job that has burned GPU time.
`prompt`/`negative_prompt` are rejected, not clamped — both are moderated at submit and the restart path does not re-moderate new text, so accepting either would turn a knob into a laundering route for a blocked prompt on a platform serving minors.
Overrides are stage-scoped: carried on a same-stage failover republish (an alternate model must reproduce the same intent), omitted from the next-stage publish.
Also adds per-method capability descriptors to `GET options` so the composer renders the right control per model. Known limitation: those descriptors are DECLARED in the allow-list rather than read from the models' `artpipe.json`, because the manifests live on the worker's pipeline root and the API host has no copy. Keep the two in sync.
Audited against origin/master — substantially done; two gaps. Staying open.
Landed:GenerationParamOverrides as a field genuinely separate from ArtAssetStageMethod.Params (RequestArtAssetStageCommand.cs:351-352 vs :375); Overridable (:29-32) / Reserved (:47-55) key sets; prompt/negative_prompt rejected before any clamp (ArtStudioGenerationParamPolicy.cs:92-98) with 400s that name the real limits (:102, :145, :156-166); capability descriptors (ArtStudioStageMethodOptions.cs:546, seeded :102-148) exposed on GET options (ArtStudioController.cs:877-897); overrides carried on same-stage failover (ArtPipeStageOrchestrator.cs:1447-1464) and correctly omitted on next-stage publish (:672-690) — both with explanatory comments and tests.
Remaining 1 — "the worker drops it with a warning" is actually silent. Clamping happens, but nothing is logged: sdxl_lightning.py:140, flux_schnell.py:147-152, qr_monster.py:161,166,170,176,181. The only warning anywhere in the parameter path is for an unparseable seed (generation_params.py:87). So a user whose override is silently clamped has no signal, and neither does anyone reading worker logs afterwards — which defeats the point of allowing overrides at all.
Remaining 2 — GET options doesn't filter by source type.GetArtStudioMethodOptionsQuery.cs:14 takes only bool IsStaff, so the composer is handed methods that cannot honour a reference image. Related to the guard-reach problem noted on #846.
Audited against `origin/master` — **substantially done; two gaps.** Staying open.
**Landed:** `GenerationParamOverrides` as a field genuinely separate from `ArtAssetStageMethod.Params` (`RequestArtAssetStageCommand.cs:351-352` vs `:375`); `Overridable` (`:29-32`) / `Reserved` (`:47-55`) key sets; `prompt`/`negative_prompt` rejected **before** any clamp (`ArtStudioGenerationParamPolicy.cs:92-98`) with 400s that name the real limits (`:102`, `:145`, `:156-166`); capability descriptors (`ArtStudioStageMethodOptions.cs:546`, seeded `:102-148`) exposed on `GET options` (`ArtStudioController.cs:877-897`); overrides carried on same-stage failover (`ArtPipeStageOrchestrator.cs:1447-1464`) and correctly omitted on next-stage publish (`:672-690`) — both with explanatory comments and tests.
**Remaining 1 — "the worker drops it with a warning" is actually silent.** Clamping happens, but nothing is logged: `sdxl_lightning.py:140`, `flux_schnell.py:147-152`, `qr_monster.py:161,166,170,176,181`. The only warning anywhere in the parameter path is for an unparseable seed (`generation_params.py:87`). So a user whose override is silently clamped has no signal, and neither does anyone reading worker logs afterwards — which defeats the point of allowing overrides at all.
**Remaining 2 — `GET options` doesn't filter by source type.** `GetArtStudioMethodOptionsQuery.cs:14` takes only `bool IsStaff`, so the composer is handed methods that cannot honour a reference image. Related to the guard-reach problem noted on #846.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds
RequestArtAssetStageCommand.GenerationParamOverridesas a SEPARATE field fromArtAssetStageMethod.Params— those are static server config and caller input must not overwrite one by colliding on a key.Enforcement is asymmetric on purpose: the API reports a bad value so the caller gets a 400 naming the real limit; the worker drops it with a warning instead, because a message that already passed validation should never fail a job that has burned GPU time.
prompt/negative_promptare rejected, not clamped — both are moderated at submit and the restart path does not re-moderate new text, so accepting either would turn a knob into a laundering route for a blocked prompt on a platform serving minors.Overrides are stage-scoped: carried on a same-stage failover republish (an alternate model must reproduce the same intent), omitted from the next-stage publish.
Also adds per-method capability descriptors to
GET optionsso the composer renders the right control per model. Known limitation: those descriptors are DECLARED in the allow-list rather than read from the models'artpipe.json, because the manifests live on the worker's pipeline root and the API host has no copy. Keep the two in sync.Audited against
origin/master— substantially done; two gaps. Staying open.Landed:
GenerationParamOverridesas a field genuinely separate fromArtAssetStageMethod.Params(RequestArtAssetStageCommand.cs:351-352vs:375);Overridable(:29-32) /Reserved(:47-55) key sets;prompt/negative_promptrejected before any clamp (ArtStudioGenerationParamPolicy.cs:92-98) with 400s that name the real limits (:102,:145,:156-166); capability descriptors (ArtStudioStageMethodOptions.cs:546, seeded:102-148) exposed onGET options(ArtStudioController.cs:877-897); overrides carried on same-stage failover (ArtPipeStageOrchestrator.cs:1447-1464) and correctly omitted on next-stage publish (:672-690) — both with explanatory comments and tests.Remaining 1 — "the worker drops it with a warning" is actually silent. Clamping happens, but nothing is logged:
sdxl_lightning.py:140,flux_schnell.py:147-152,qr_monster.py:161,166,170,176,181. The only warning anywhere in the parameter path is for an unparseable seed (generation_params.py:87). So a user whose override is silently clamped has no signal, and neither does anyone reading worker logs afterwards — which defeats the point of allowing overrides at all.Remaining 2 —
GET optionsdoesn't filter by source type.GetArtStudioMethodOptionsQuery.cs:14takes onlybool IsStaff, so the composer is handed methods that cannot honour a reference image. Related to the guard-reach problem noted on #846.