Sub-ticket of epic #346. No dependencies — this is the starting point.
What: the CQRS domain foundation for multi-stage art assets, generalizing the existing single-stage ThreeDModel vertical (SpikerSoft.Data/Mongos/ThreeDModel.cs, SpikerSoft.Business/Domain/ThreeDModel/) into a stage-graph model matching art_pipe's ASSET_TYPE_STAGES (concept → modeling → texturing → rigging → animation → export → enrichment).
Scope:
Mongo docs: ArtAsset (owner, asset type, prompt/source refs, current stage, status, GridFS artifact refs per stage) + ArtAssetStage workflow doc per stage run (mirror BlogMediaWorkflowState: state machine, timestamps, error, retry count, model used, timings) + indexes
Decide slice placement: new SpikerSoft.Business.ArtStudio project (model: SpikerSoft.Business.CodeExecution) vs folder in SpikerSoft.Business; absorb/deprecate the existing Domain/ThreeDModel slice either way
ArtStudioController in SpikerSoft.Api/Domain/ArtStudio/ — thin MediatR passthrough, [Authorize], ownership enforced in handlers (student sees only own assets; staff sees all)
GridFS artifact service (pattern: IGridFsVaultFileService) — per-stage artifacts keyed by assetId + stage
Unit tests for handlers (pattern: SpikerSoft.Tests.Unit/Domain/ThreeDModel)
Note: stage plan should be data-driven per asset type (prop vs character vs texture-only), mirroring art_pipe/src/artpipe/config.py::ASSET_TYPE_STAGES — characters get rigging/animation, props skip them.
Acceptance: a submitted asset appears with a planned stage list and a stage.requested message on the exchange; list/get/download/cancel/restart endpoints work against seeded data; a student cannot read or download another student's asset.
Sub-ticket of epic #346. No dependencies — this is the starting point.
**What:** the CQRS domain foundation for multi-stage art assets, generalizing the existing single-stage `ThreeDModel` vertical (`SpikerSoft.Data/Mongos/ThreeDModel.cs`, `SpikerSoft.Business/Domain/ThreeDModel/`) into a stage-graph model matching art_pipe's `ASSET_TYPE_STAGES` (concept → modeling → texturing → rigging → animation → export → enrichment).
**Scope:**
- [ ] Mongo docs: `ArtAsset` (owner, asset type, prompt/source refs, current stage, status, GridFS artifact refs per stage) + `ArtAssetStage` workflow doc per stage run (mirror `BlogMediaWorkflowState`: state machine, timestamps, error, retry count, model used, timings) + indexes
- [ ] Decide slice placement: new `SpikerSoft.Business.ArtStudio` project (model: `SpikerSoft.Business.CodeExecution`) vs folder in `SpikerSoft.Business`; absorb/deprecate the existing `Domain/ThreeDModel` slice either way
- [ ] Commands: `SubmitArtAssetCommand` (creates asset, stages plan, publishes first stage job — `IRemoteCommand` so `RemoteDispatchBehavior` routes it), `CancelArtAssetCommand`, `RestartFromStageCommand` (mirrors art_pipe's `restart_from/<stage>`), `DeleteArtAssetCommand`
- [ ] Queries: `GetArtAssetById`, `GetArtAssetStages`, `ListMyArtAssets` (paged, student-scoped), `DownloadArtifact` (GridFS stream: GLB / textures / previews)
- [ ] `ArtStudioController` in `SpikerSoft.Api/Domain/ArtStudio/` — thin MediatR passthrough, `[Authorize]`, ownership enforced in handlers (student sees only own assets; staff sees all)
- [ ] Event contracts in `SpikerSoft.Contracts.Workers`: `art.asset.lifecycle` exchange, `art.asset.stage.requested` / `.completed` / `.failed` routing keys, W3C trace-context propagated (telemetry-correlation rule)
- [ ] GridFS artifact service (pattern: `IGridFsVaultFileService`) — per-stage artifacts keyed by assetId + stage
- [ ] Unit tests for handlers (pattern: `SpikerSoft.Tests.Unit/Domain/ThreeDModel`)
**Note:** stage plan should be data-driven per asset type (prop vs character vs texture-only), mirroring `art_pipe/src/artpipe/config.py::ASSET_TYPE_STAGES` — characters get rigging/animation, props skip them.
**Acceptance:** a submitted asset appears with a planned stage list and a `stage.requested` message on the exchange; list/get/download/cancel/restart endpoints work against seeded data; a student cannot read or download another student's asset.
Implementation up: spikersoft-backend PR #61 (branch feature/artstudio-p1-347, 39 files). Build clean, 104/104 unit tests green (86 new + 18 ThreeDModel regression), independently re-verified.
Scope notes vs the ticket:
Slice placement: folder in SpikerSoft.Business (extraction to a slice project deferred, as allowed)
Submit is a local command that dispatches the IRemoteCommand (RequestArtAssetStageCommand) — RemoteDispatchBehavior short-circuits IRemoteCommands before local handlers, so one command can't do both; JobId returned as correlation id
Cancel is DB-state only in P1 — #348's worker must check ArtAssetStageRun.State before/while running (noted there)
Awaiting CI + review on the PR.
Implementation up: spikersoft-backend PR [#61](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/61) (branch `feature/artstudio-p1-347`, 39 files). Build clean, 104/104 unit tests green (86 new + 18 ThreeDModel regression), independently re-verified.
Scope notes vs the ticket:
- Slice placement: folder in `SpikerSoft.Business` (extraction to a slice project deferred, as allowed)
- Submit is a local command that dispatches the `IRemoteCommand` (`RequestArtAssetStageCommand`) — RemoteDispatchBehavior short-circuits IRemoteCommands before local handlers, so one command can't do both; JobId returned as correlation id
- Cancel is DB-state only in P1 — #348's worker must check `ArtAssetStageRun.State` before/while running (noted there)
Awaiting CI + review on the PR.
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.
Sub-ticket of epic #346. No dependencies — this is the starting point.
What: the CQRS domain foundation for multi-stage art assets, generalizing the existing single-stage
ThreeDModelvertical (SpikerSoft.Data/Mongos/ThreeDModel.cs,SpikerSoft.Business/Domain/ThreeDModel/) into a stage-graph model matching art_pipe'sASSET_TYPE_STAGES(concept → modeling → texturing → rigging → animation → export → enrichment).Scope:
ArtAsset(owner, asset type, prompt/source refs, current stage, status, GridFS artifact refs per stage) +ArtAssetStageworkflow doc per stage run (mirrorBlogMediaWorkflowState: state machine, timestamps, error, retry count, model used, timings) + indexesSpikerSoft.Business.ArtStudioproject (model:SpikerSoft.Business.CodeExecution) vs folder inSpikerSoft.Business; absorb/deprecate the existingDomain/ThreeDModelslice either waySubmitArtAssetCommand(creates asset, stages plan, publishes first stage job —IRemoteCommandsoRemoteDispatchBehaviorroutes it),CancelArtAssetCommand,RestartFromStageCommand(mirrors art_pipe'srestart_from/<stage>),DeleteArtAssetCommandGetArtAssetById,GetArtAssetStages,ListMyArtAssets(paged, student-scoped),DownloadArtifact(GridFS stream: GLB / textures / previews)ArtStudioControllerinSpikerSoft.Api/Domain/ArtStudio/— thin MediatR passthrough,[Authorize], ownership enforced in handlers (student sees only own assets; staff sees all)SpikerSoft.Contracts.Workers:art.asset.lifecycleexchange,art.asset.stage.requested/.completed/.failedrouting keys, W3C trace-context propagated (telemetry-correlation rule)IGridFsVaultFileService) — per-stage artifacts keyed by assetId + stageSpikerSoft.Tests.Unit/Domain/ThreeDModel)Note: stage plan should be data-driven per asset type (prop vs character vs texture-only), mirroring
art_pipe/src/artpipe/config.py::ASSET_TYPE_STAGES— characters get rigging/animation, props skip them.Acceptance: a submitted asset appears with a planned stage list and a
stage.requestedmessage on the exchange; list/get/download/cancel/restart endpoints work against seeded data; a student cannot read or download another student's asset.Implementation up: spikersoft-backend PR #61 (branch
feature/artstudio-p1-347, 39 files). Build clean, 104/104 unit tests green (86 new + 18 ThreeDModel regression), independently re-verified.Scope notes vs the ticket:
SpikerSoft.Business(extraction to a slice project deferred, as allowed)IRemoteCommand(RequestArtAssetStageCommand) — RemoteDispatchBehavior short-circuits IRemoteCommands before local handlers, so one command can't do both; JobId returned as correlation idArtAssetStageRun.Statebefore/while running (noted there)Awaiting CI + review on the PR.