# API2 Specialist Report — Asset CR Endpoints (agent-feature / PAPI-3523) Feature: three read/create REST endpoints for the pre-existing `xyz."Asset"` table under `/api/v2/projects/:projectId/assets`, gated on project membership. Spec: `docs/specs/20260709-125531-agent-feature.md`. ## Revision addressed: "Fix the failed API tests" The failing e2e suite (`test/e2e/api/assets.e2e.spec.ts`) drives the real database. I traced the full request → service → DB contract and cross-checked it against the (separately corrected) Postgres surface in the sibling `postgres` repo. **Finding: the api2 surface was already contract-correct; no api2 production code change was required for this revision.** The original test failures originated in the DB layer — the plan's `usp_InsertAsset` **procedure** (6 params, `INOUT` return) did not match the api2 call `SELECT * FROM xyz."fn_InsertAsset"($1,$2,$3,$4)`, so the create path 500'd. That mismatch was fixed on the Postgres side by replacing the procedure with a table-returning `fn_InsertAsset` function whose signature, returned columns, and error text match what api2 already calls. Contract points I verified against the corrected DB functions: | api2 expectation | Verified | |---|---| | `fn_InsertAsset($1,$2,$3,$4)` = `(projectId, assetTypeId, name, createdBy)` returning the full row | ✓ matches `assets.service.createAsset` | | Create-with-unknown-assetType → 404 | ✓ DB raises `'AssetType with id % not found in project'`; `db.query` rethrows the raw pg error (SQLSTATE `P0001` is not an integrity-constraint code, so the message is preserved); `mapError` matches substring `'AssetType with id'` → `NotFoundError` → `createApiErrorResponse` → 404 | | `fn_GetAsset($1,$2)` returns 12-column row | ✓ `getAssetById` / `mapRow` | | `fn_GetAssetList($1,$2,$3)` returns rows incl. internal `"Id"` cursor | ✓ `listAssets` maps `indexId: row.Id`, envelope via `buildPaginatedQueryResponse` | | `db-helper` reads `xyz."Asset"` by `("ProjectShardId","AssetId")` | ✓ matches table PK in `129_xyz_asset.sql` | ## Files changed (all new/untracked; this feature) ### Production source - `src/api/v2/projects/assets/assets.routes.ts` — router (`mergeParams`), Swagger JSDoc, middleware order `validator → hasRolesOrProjectAccess → handler` (PROJECT_VIEW for reads, PROJECT_EDIT for create). Mirrors `asset-types`. - `src/api/v2/projects/assets/assets.controller.ts` — `listAssets` (200 + paginated envelope), `getAsset` (200), `createAsset` (201); errors via `createApiErrorResponse`. - `src/api/v2/projects/assets/assets.validator.ts` — projectId/assetId UUID checks, paging validation, create-body (`name` non-blank string, `assetTypeId` UUID). - `src/services/assets.service.ts` — DB access (`fn_GetAssetList`, `fn_GetAsset`, `fn_InsertAsset`), `Asset` interface, `mapRow`, `mapError` (`'AssetType with id'` → `NotFoundError`). ### Wiring / schema - `src/api/v2/api.v2.routes.ts` — mount `assetsRouter` at `/:projectId/assets`. - `src/swagger.components.schemas.json` — added `Asset` component schema (referenced from the route JSDoc via `$ref`, per repo convention). ### Tests - `test/unit/api/v2/assets/assets.controller.spec.ts` — 8 controller unit tests (200/201/404/500 paths, paging forwarding). - `test/e2e/api/assets.e2e.spec.ts` — e2e coverage for all three endpoints (create + read-back, pagination via `size`/`lastFetchedIndexId`, 400/404 cases). - `test/e2e/util/db-helper.ts` — added `getAssetById` / `getAssetsByProject`. ## Verification Runnable locally: - `npm test` (full unit suite): **1707 passing, 0 failing**, 5 pending. - `npx tsc --noEmit`: clean. - `assets.controller.spec.ts` in isolation: 8 passing. - Swagger JSON parses. Not runnable locally: the e2e suite requires a provisioned Postgres/Citus DB and auth certs (`test/e2e/auth/certs/private.key`), neither present in this environment. The pipeline runs e2e against the DB built from the `postgres` repo, which now includes the aligned `fn_InsertAsset`. ## Deviations from the spec None on the api2 side. The spec's "Postgres (new procs/functions only)" work was delivered in the sibling `postgres` repo; the create path is a function (`fn_InsertAsset`), not the procedure named in the original plan — the api2 consumer calls the function via `SELECT *`, and the DB layer was aligned to that. ## Open questions for the human reviewer 1. **Create does not accept `parentAssetId`.** The `Asset` table has a nullable `ParentAssetId`, but per the spec's create contract the endpoint inserts it as NULL and the validator/controller do not accept it. Confirm this is intended for v1 (read functions do return the column). 2. **Equal access for all project members** (PROJECT_VIEW read / PROJECT_EDIT create) — matches spec "no role differentiation"; flagging in case finer-grained asset permissions are wanted later. 3. Please confirm the e2e suite is green in the pipeline now that the Postgres `fn_InsertAsset` function is in place — that was the sole root cause of the original API test failures and it lay in the DB layer, not api2.