docs/specs/20260625-141024-agent-feature.mdadded
---
spec: agent-feature
status: done
owner: feature-pipeline-agent
related-routes:
files:
  # (none listed)
---

# agent-feature

## Goal

Enforce uniqueness of "Name" per entity scope on five existing project-scoped
xyz tables: AssetType, SystemType, Asset, CommissioningSystem (the "System"
in the spec), and CommissioningWorkflow (the "Workflow" in the spec).
Uniqueness is scoped per project (ProjectShardId) and, where the entity has a
natural parent scope, per that parent. Duplicates must be cleaned up before the
unique constraint is applied. No new tables are introduced.


## Behavior changes

- {db_calls: [usp_InsertAssetType, usp_InsertSystemType, usp_InsertAsset, usp_UpdateAsset,
    usp_InsertCommissioningSystem, usp_UpdateCommissioningSystem, usp_InsertCommissioningWorkflow,
    usp_UpdateCommissioningWorkflow], kind: error_mapping, notes: "For each of the\
    \ five entity write paths (create + update where applicable),\ncatch the DB unique-violation\
    \ (DB_ConstraintViolationError / SQLSTATE 23505\nor the explicit RAISE from the\
    \ amended procs) and map it to a 409 Conflict\nusing a typed error from src/types/errortypes.ts\
    \ (e.g. a Conflict/duplicate\nerror), with a clear message naming the entity and\
    \ the colliding name.\nDo NOT add inline SQL \u2014 continue to call the existing\
    \ usp_* procedures via\nthe existing const query strings. The actual service filenames\
    \ must be\nconfirmed against the repo; the names above are the conventional lowercase\n\
    dot-separated forms.\n", services: [asset.types.service.ts, system.types.service.ts,
    assets.service.ts, commissioning.systems.service.ts, commissioning.workflows.service.ts]}
- {kind: tests_and_dtos, notes: "No DTO shape changes are required (Name field already\
    \ exists). However,\nbecause the write procs are amended, existing API2 unit/e2e\
    \ tests, mocks,\nand fixtures for these five entities must be updated so that:\n\
    \  - duplicate-name create/update returns 409,\n  - non-duplicate create/update\
    \ still returns the existing success codes.\nUpdate fixtures/mocks so `npm test`\
    \ stays green.\n"}

## 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

- No changes to read endpoints or read procedures/functions.
- No new columns added to any table; only constraint/index + proc error handling.
- No case-insensitive normalization unless the open question is resolved that way at Gate 2.
- No new entities or tables.

## 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

- **The five entities did not previously exist in api2.** A repo-wide search found
  no services, routes, or `usp_Insert*/usp_Update*` references for AssetType,
  SystemType, Asset, CommissioningSystem, or CommissioningWorkflow. Per the
  explicit "implement the FULL API2 surface needed" instruction, the write paths
  (create + update where the spec lists an update proc) were created from scratch
  as new project-scoped resources, mirroring the established `categoryTypes`
  sibling (routes → controller → validator → service).
- **409 mapping** reuses the existing `ResourceConflictError` (409) from
  `src/types/errortypes.ts` rather than introducing a new error type. Each write
  service catches `DB_ConstraintViolationError` (already produced by `db.ts` for
  SQLSTATE 23505) — or an explicit proc `RAISE` whose message contains
  "already exists" — and rethrows it as a `ResourceConflictError` naming the
  entity and colliding name.
- **No inline SQL.** Services continue to `CALL xyz."usp_*"(...)` via const query
  strings, matching the repo convention.
- **Scoping** follows the natural parent hierarchy: AssetType/SystemType are
  project-scoped; Asset is scoped by `assetTypeId`; CommissioningSystem by
  `systemTypeId`; CommissioningWorkflow by `commissioningSystemId`. Parent ids are
  supplied in the request body for create.
- **Authority:** writes are guarded by `PROJECT_EDIT` (no asset/commissioning
  specific authority exists in `authorities.types.ts`).
- **No read endpoints added** (spec out-of-scope). Resources expose only the
  enumerated write paths.
- **e2e coverage:** happy-path 201/204 and duplicate-name 409 assertions require
  the DB-side proc + unique-constraint migration, which lives outside this API
  repo. e2e specs therefore cover the validation (400) and authorization (403)
  paths, which execute before the DB layer; the 201/409 behavior is covered by
  service unit tests that stub the `query` layer.
- Build/tests were **not run**: `node_modules` is not installed in this
  environment and the task instruction forbids installing the toolchain. The
  pipeline runs the suite afterward.