--- spec: agent-feature status: done owner: feature-pipeline-agent related-routes: files: - routes - controller - validator - service --- # agent-feature ## Goal Adds PATCH /api/v2/assets/{assetId} to API2, allowing partial (RFC 7396 merge-patch) updates to the scalar fields of an Asset (currently only the Name field). Authorized using the existing project Asset-edit role; no new permissions are introduced. Note: Asset is project-scoped in Postgres, so the endpoint is implemented under the project-scoped path /api/v2/projects/{projectId}/assets/{assetId} to resolve ProjectShardId. ## Behavior changes - PATCH /api/v2/projects/{projectId}/assets/{assetId} ## Acceptance criteria - [ ] The behavior described above is implemented on the listed files. - [ ] Existing behavior is unchanged when the new feature is not exercised. - [ ] Unit and (where observable) e2e tests cover the new behavior. ## Out of scope - JSON Patch (RFC 6902) operation syntax - Patching relational fields on Asset (AssetTypeId, ParentAssetId) or nested/linked entities - Bulk / multi-asset patch in a single request - New roles or field-level permission granularity — uses existing ASSET_EDIT permission only - New Asset columns / schema additions ## Notes / open questions - Generated by the feature pipeline from the approved architect plan. If any acceptance criterion is ambiguous, ask the human before implementing. ## Decisions - **Authorization role**: The spec's "Out of scope" mentions an `ASSET_EDIT` permission, but no such value exists in `src/api/auth/authorities.types.ts`. The sibling `POST .../assets` create endpoint authorizes with `[Authorities.INTERNAL_ROLE], [Authorities.PROJECT_EDIT]`. To honour "no new permissions are introduced", PATCH reuses that exact guard rather than adding an `ASSET_EDIT` enum value. - **Merge-patch shape**: Followed the established PATCH pattern in `models.validator.ts` — an allow-list of patchable scalar fields. Unknown fields → 406, empty body → 400, blank/non-string `name` → 400. Only `name` is patchable today (matches the Asset scalar-field scope). - **Response**: Returns `200` with the full updated `Asset` body (mirrors `updateCommissioningWorkflow`), rather than `204`, so clients get the new `lastModifiedOn`/`lastModifiedBy`. - **DB function**: Added `fn_UpdateAsset($projectId, $assetId, $name, $username)` to the service SQL map, mirroring `fn_InsertAsset` / `fn_UpdateCommissioningWorkflow` naming. This stored function is assumed to exist in the DB (created by the DB migration layer, consistent with sibling functions).