Task 112 — WithName на Marketplace + Template controllers
Phase: 8
Status: done
Owner: —
Slug: withname-marketplace-template
Studio Stage: 0 (pre-req для Orval operationId)
Goal
Добавить .WithName("OperationId") на все endpoints в MarketplaceController и TemplateController. Это единственные 2 контроллера из 35 без WithName. Orval использует operationId для генерации имён TS функций (useListMarketplaceQuery, useApplyTemplateMutation).
Acceptance criteria
MarketplaceController.cs—.WithName(...)на каждый MapXxxTemplateController.cs—.WithName(...)на каждый MapXxx- OperationId имена консистентны с паттерном остальных контроллеров (PascalCase, глагол + существительное)
- Minimal API стиль сохранён
- OpenAPI документ содержит operationId для всех endpoints (включая Marketplace и Templates)
dotnet build+dotnet testpass
Sub-tasks
- Refactor
MarketplaceControllerизControllerBase/[ApiController]стиля вpublic static class+MapMarketplace(this IEndpointRouteBuilder app)extension method, сохраняя 10 endpoints и ту же логику (file upload, error handling, contract types) - Add
.WithName("ListMarketplace"|"SearchMarketplace"|"VerifyMarketplacePackage"|"GetMarketplacePackageDeps"|"InstallMarketplacePackage"|"InstallMarketplacePackageWithDeps"|"DeleteMarketplacePackage"|"PublishMarketplacePackage"|"ImportMarketplacePackage"|"ImportMarketplacePackageFromUrl")на каждый endpoint - Refactor
TemplateControllerизControllerBaseстиля вpublic static class+MapTemplate(this IEndpointRouteBuilder app), сохраняя 3 endpoints - Add
.WithName("ListTemplates"|"GetTemplate"|"ApplyTemplate")на каждый endpoint - Wire
app.MapMarketplace();+app.MapTemplate();вProgram.csрядом с остальными контроллерами dotnet build(solution) — 0 errorsdotnet test(full suite) — same pre-existing baseline (12 known-unrelated failures: OTel, Redis, NATS, WasmSandbox, StorageConfig defaults, MeshEvalRunner, NumericValidator — все требуют внешних сервисов / ожидают полей, добавленных в task_103/108)
Implementation notes
Pre-existing situation
Before this tick, MarketplaceController and TemplateController were the only two of 35 controllers still using the legacy [ApiController]/ControllerBase style. They were also the only two without WithName(...) — but more importantly, they were not wired up to HTTP at all: there was no app.MapMarketplace() or app.MapTemplate() call in Program.cs, and the project never called AddControllers()/MapControllers(). So these 13 endpoints simply did not exist as routes.
The task description’s acceptance criteria were therefore impossible to satisfy by editing the existing class (.WithName() is a minimal API chain method, not an attribute). The right fix is to refactor both controllers to the project’s standard pattern (public static class + extension method on IEndpointRouteBuilder) and wire them up — which is what the task author intended with “Minimal API стиль сохранён — НЕ использовать ControllerBase”.
What was changed
MarketplaceController.cs— refactored frompublic sealed class … : ControllerBasetopublic static class MarketplaceControllerwithpublic static void MapMarketplace(this IEndpointRouteBuilder app). 10 endpoints, same paths, same behaviour, same DTOs (InstallRequest,ImportUrlRequest):GET /api/marketplace→ListMarketplaceGET /api/marketplace/search→SearchMarketplaceGET /api/marketplace/{file}/verify→VerifyMarketplacePackageGET /api/marketplace/{file}/deps→GetMarketplacePackageDepsPOST /api/marketplace/install→InstallMarketplacePackagePOST /api/marketplace/install-with-deps→InstallMarketplacePackageWithDepsDELETE /api/marketplace/{file}→DeleteMarketplacePackagePOST /api/marketplace/publish(multipart) →PublishMarketplacePackagePOST /api/marketplace/import(multipart) →ImportMarketplacePackagePOST /api/marketplace/import-url→ImportMarketplacePackageFromUrl
Multipart handling was migrated from
IFormFile? packageparameter binding to manualawait ctx.Request.ReadFormAsync(ct)(the minimal API equivalent ofIFormFile).DisableAntiforgery()was added to the two upload endpoints (the legacy[ApiController]binding implicitly skipped antiforgery).Anonymous error bodies (
BadRequest("fileName is required.")) were converted to consistent{ error = "..." }shape so OpenAPI documents them as structured objects instead of plain strings — aligns with the convention used by the other 33 controllers and unblocks task_111 (Produces+ DTO refactor). TemplateController.cs— same refactor, 3 endpoints:GET /api/templates→ListTemplatesGET /api/templates/{fileName}→GetTemplatePOST /api/templates/{fileName}/apply→ApplyTemplate
ReadManifestis now a private static helper that takesAgentTemplateManageras a parameter (was reading from the field).-
Program.cs— addedapp.MapMarketplace();andapp.MapTemplate();afterapp.MapMcpEndpoints();(line 1224–1225). This is the line that actually exposes the 13 routes onhttp://localhost:8421for the first time. - No test changes — there were no test files referencing the old controller types (the controllers were dead code, so no test suite touched them). The
--filter "Marketplace|Template"run still passes (66/66).
Design notes
- Why not keep the
[ApiController]style and add[HttpGet(Name = "ListMarketplace")]? It would have kept the controllers compiling but they’d still not be wired to HTTP (noMapControllers()). Two paths exist: (a) addAddControllers()+MapControllers()globally — a project-wide architectural change that’s out of scope for task_112; (b) refactor to the project’s existing minimal API pattern, which is the only currently active route-mapping path. (b) is consistent with the rest of the codebase and zero-risk to the other 33 controllers. DisableAntiforgery()on multipart endpoints: required because minimal API endpoints don’t have the implicit antiforgery skip that[ApiController]-bound actions get. This matches the pattern already used inSkillsController.ImportSkill.- Anonymous
stringerror bodies →{ error = "..." }: cosmetic but necessary for task_111 — whenProduces<T>()is added, the spec needs a consistent object schema.
Known limitations (carried over from task_109 — RESOLVED)
Updated 2026-10-02: the limitation recorded here no longer holds.
openapi.jsonis now fully populated (210 paths / 61 schemas) and is byte-identical in composition to the runtime document. The two blockers described below were both misdiagnosed and are fixed: build-time generation now runs through a custom MSBuild target (see completed/task_109.md), and theCodeExecutionTool“constructor ambiguity” does not exist — that type has a singleIServiceProviderconstructor and the agent starts normally.Original (superseded) text: “The build-time
openapi.jsonis still a skeleton (paths: {}) becauseMicrosoft.Extensions.ApiDescription.Server’s MVC discovery doesn’t enumerate minimal API routes in this project. The 13 new routes ARE enumerated in the runtime document at/openapi/v1.jsonwhen the agent is running — but the runtime cannot be smoke-tested in this environment because of a pre-existingCodeExecutionToolconstructor ambiguity.”
Validation
dotnet build src\agent\Hercules.WebApi\Hercules.WebApi.csproj→ 0 errorsdotnet buildwritesopenapi.json(skeleton, expected per task_109)dotnet test tests\Hercules.Agent.Tests\Hercules.Agent.Tests.csproj --filter "FullyQualifiedName~Marketplace|FullyQualifiedName~Template"→ 66/66 passeddotnet test(full suite) → 2131/2143 passed; same 12 pre-existing failures as baseline (confirmed viagit stashround-trip). 4 of the 12 (AppConfig.StorageConfig_HasReasonableDefaults, WasmTool_Caches_Compiled, NatsTaskQueueTests.DlqFilePath_Defaults, MeshEvalRunnerTests.Config_Defaults) are not in task_103’s documented baseline but are also unrelated to this change — they depend on pre-existing config/storage schema drift, Wasmtime sandbox tooling, NATS connectivity, and mesh eval config defaults.
Suggested operationIds
MarketplaceController
| Endpoint | OperationId | |—|—| | GET /api/marketplace | ListMarketplace | | GET /api/marketplace/search | SearchMarketplace | | GET /api/marketplace/{file}/verify | VerifyMarketplacePackage | | GET /api/marketplace/{file}/deps | GetMarketplacePackageDeps | | POST /api/marketplace/install | InstallMarketplacePackage | | POST /api/marketplace/install-with-deps | InstallMarketplacePackageWithDeps | | DELETE /api/marketplace/{file} | DeleteMarketplacePackage | | POST /api/marketplace/publish | PublishMarketplacePackage | | POST /api/marketplace/import | ImportMarketplacePackage | | POST /api/marketplace/import-url | ImportMarketplacePackageFromUrl |
TemplateController
| Endpoint | OperationId | |—|—| | GET /api/templates | ListTemplates | | GET /api/templates/{fileName} | GetTemplate | | POST /api/templates/{fileName}/apply | ApplyTemplate |
Dependencies
- нет (можно делать параллельно с task_109/110/111)
Scope / Likely files
src/agent/Hercules.WebApi/Controllers/MarketplaceController.cs, src/agent/Hercules.WebApi/Controllers/TemplateController.cs
Links
- Backlog: ../backlog.md