summary: 'Add API2 create-and-read endpoints for AssetType scoped to a project: POST to create an AssetType, GET by id, and GET list. No update/delete in this version. AssetType already exists as a project-scoped Postgres table; this adds the procedure/function DB surface and the API2 runtime layer. ' target_service: api2 postgres_changes: - kind: procedure schema: xyz name: usp_InsertAssetType intent: 'Insert a new AssetType row for a project. Resolves ProjectShardId from _projectId via xyz."fn_GetProjectShardId". Validates the referenced CommissioningWorkflow exists for the same ProjectShardId, raising NOT_FOUND otherwise (so the API2 controller does not need a separate pre-fetch). ' parameters: - _projectId UUID - _commissioningWorkflowId UUID - _name TEXT - _createdBy TEXT writes_table: xyz."AssetType" notes: 'Sets AssetTypeId via gen_random_uuid() (table default), ProjectShardId from fn_GetProjectShardId, CommissioningWorkflowId, Name, CreatedBy, InsertedOn (default). Must return the inserted row so API2 can map the egress DTO in a single round-trip. Include the standard EXCEPTION block logging to xyz."DbException". File: Database/xyz/Procedures/usp_InsertAssetType.sql. NOTE: this is a NEW procedure, not a column change; existing AssetType table is unchanged. Column names used (AssetTypeId, ProjectShardId, CommissioningWorkflowId, Name, CreatedBy, InsertedOn, LastModifiedOn, LastModifiedBy) match the live table exactly. ' - kind: function schema: xyz name: fn_GetAssetType intent: 'Return a single AssetType by id for a project. Resolves ProjectShardId from _projectId first, then filters by ProjectShardId + AssetTypeId. Returns zero rows if not found (API2 maps to NOT_FOUND). ' parameters: - _projectId UUID - _assetTypeId UUID returns_shape: 'TABLE("assetTypeId" UUID, "commissioningWorkflowId" UUID, "name" TEXT, "createdBy" TEXT, "insertedOn" TIMESTAMPTZ, "lastModifiedOn" TIMESTAMPTZ, "lastModifiedBy" TEXT) ' reads_table: xyz."AssetType" notes: 'File: Database/xyz/Functions/fn_GetAssetType.sql. Filter on ProjectShardId, never ProjectId. ' - kind: function schema: xyz name: fn_GetAssetTypeList intent: 'Return all AssetTypes for a project (flat list, v1). Resolves ProjectShardId from _projectId, filters by ProjectShardId, ordered by InsertedOn. ' parameters: - _projectId UUID returns_shape: 'TABLE("assetTypeId" UUID, "commissioningWorkflowId" UUID, "name" TEXT, "createdBy" TEXT, "insertedOn" TIMESTAMPTZ, "lastModifiedOn" TIMESTAMPTZ, "lastModifiedBy" TEXT) ' reads_table: xyz."AssetType" notes: 'File: Database/xyz/Functions/fn_GetAssetTypeList.sql. Flat list; no pagination in v1 (see open_questions / risks). ' citus_changes: none index_changes: none reporting_changes: none seed_data_changes: none mongo_changes: none api2_changes: - method: POST path: /api/v2/projects/{projectId}/asset-types tag: AssetTypes permissions_note: 'bearerAuth + project membership verification via existing project-scoped auth middleware. No new permission introduced (spec: authenticated project membership only). ' db_calls: - usp_InsertAssetType request_dto: name: CreateAssetTypeRequest properties: commissioningWorkflowId: type: string format: uuid required: true name: type: string max_length: 512 required: true response_dto: name: AssetTypeResponse properties: assetTypeId: type: string format: uuid commissioningWorkflowId: type: string format: uuid name: type: string createdBy: type: string insertedOn: type: string format: date-time lastModifiedOn: type: string format: date-time nullable: true lastModifiedBy: type: string nullable: true files: - src/api/v2/projects/assettypes/asset.types.routes.ts - src/api/v2/projects/assettypes/asset.types.controller.ts - src/api/v2/projects/assettypes/asset.types.validator.ts - src/services/asset.types.service.ts notes: "usp_InsertAssetType internally resolves ProjectShardId and validates that\n\ the referenced CommissioningWorkflow exists for the project, raising\nNOT_FOUND\ \ from inside the procedure. The controller does NOT call a separate\nfetch first\ \ \u2014 single round-trip. createdBy comes from the authenticated\nprincipal.\n" - method: GET path: /api/v2/projects/{projectId}/asset-types/{assetTypeId} tag: AssetTypes permissions_note: bearerAuth + project membership verification. db_calls: - fn_GetAssetType response_dto: name: AssetTypeResponse properties: assetTypeId: type: string format: uuid commissioningWorkflowId: type: string format: uuid name: type: string createdBy: type: string insertedOn: type: string format: date-time lastModifiedOn: type: string format: date-time nullable: true lastModifiedBy: type: string nullable: true files: - src/api/v2/projects/assettypes/asset.types.routes.ts - src/api/v2/projects/assettypes/asset.types.controller.ts - src/services/asset.types.service.ts notes: 'Service maps zero rows from fn_GetAssetType to NotFoundError. ' - method: GET path: /api/v2/projects/{projectId}/asset-types tag: AssetTypes permissions_note: bearerAuth + project membership verification. db_calls: - fn_GetAssetTypeList response_dto: name: PaginationEnvelope properties: records: type: array items: AssetTypeResponse recordCount: type: integer lastFetchedIndexId: type: string nullable: true files: - src/api/v2/projects/assettypes/asset.types.routes.ts - src/api/v2/projects/assettypes/asset.types.controller.ts - src/services/asset.types.service.ts notes: 'List endpoint returns PaginationEnvelope per API2 convention even though v1 returns a flat list. Wrap with buildPaginatedQueryResponse. ' api1_changes: none inter_service_calls: none new_permissions: none java_frozen_resources: - resource: projects confirmation: 'Project is a dual-API resource. This feature reads xyz."Project" (via fn_GetProjectShardId inside the new procs/functions) for scoping only and does NOT modify the Java hc-project ProjectResource or any project write path. No API1 hc-project changes. ' risks: - 'AssetType has FKs and is referenced by AssetTypeReadinessGateTask, AssetTypeSystemTypeMapping, AssetTask (indirectly), etc. Exposing create without validation of CommissioningWorkflow could orphan rows; the plan validates the workflow inside usp_InsertAssetType. Confirm this is sufficient. ' - 'Open question (pagination): list-all is a flat list in v1 with no filtering/sorting. If AssetType counts grow large per project this may need real pagination later; the PaginationEnvelope shape leaves room to add it. ' - 'Open question (duplicate names): the plan does NOT reject duplicate AssetType names within a project at the API level (no unique constraint exists on the live table). Confirm whether duplicates are acceptable for v1. ' out_of_scope: - Update (PUT/PATCH) endpoint for AssetType. - Delete endpoint for AssetType. - Role-based access control beyond authenticated project membership. - Pagination/filtering/sorting on the list endpoint (flat list only in v1). - Duplicate-name rejection at the API level. testing_plan: "API2: add unit tests for asset.types.controller and asset.types.service\ \ (mock\ndb_calls: usp_InsertAssetType, fn_GetAssetType, fn_GetAssetTypeList) covering\n\ create success, create with missing/invalid commissioningWorkflowId \u2192 NOT_FOUND,\n\ get-by-id found/not-found, and list empty/non-empty. Add e2e tests under\ntest/e2e/api/v2/projects/assettypes/\ \ verifying auth (401 without token), project\nmembership enforcement (403), and\ \ the 201/200/404 response shapes. Postgres:\nextend IntegrationTest/main.py with\ \ a scenario inserting an AssetType via\nusp_InsertAssetType and reading it back\ \ via fn_GetAssetType / fn_GetAssetTypeList\nscoped by ProjectShardId. Ensure `npm\ \ test` and `./build` stay green." _meta: model: claude-opus-4-8 atom_ids: - rule.architect_rules - convention.api1-hc-project - convention.api2 - endpoint.api2.delete__api_v2_projects__projectId__activities_categories - endpoint.api2.delete__api_v2_projects__projectId__activities_categories__activityCategoryId_ - endpoint.api2.post__api_v2_projects__projectId__coordinates_delete - endpoint.api2.delete__api_v2_projects__projectId__coordinates__coordinateId_ - endpoint.api2.post__api_v2_projects_create-project - convention.api1-hc-iam - endpoint.api2.post__api_v2_projects__projectId__activities_categories - endpoint.api2.delete__api_v2_projects__projectId__coordinate-conflicts - convention.api1-hc-bpm - pg.reporting.CalculationMethod - pg.reporting.ProgressOutput - pg.reporting.ProjectCalculationMethod - pg.reporting.ProjectPerformanceSnapshot - pg.reporting.ProjectProgress - pg.staging.DuplicatedMigratedMongoElement - pg.xyz.Project - mongo.hc-project.project - mongo.embedded.Project (iam) - dto.api1-hc-project.Project - endpoint.api1-hc-iam.syncProject - dto.api2.Project spec: title: AssetType CR Endpoints (API2) project_kind: modify user_facing_behavior: Authenticated project members can create a new AssetType within a project and read AssetTypes either by ID or as a full list scoped to their project. No update or delete operations are exposed in this version. data_touched: - AssetType - Project api_surface: both non_functional_requirements: - Follow existing API2 (Node.js / Postgres) conventions for routing, auth middleware, and project-scoping patterns - Project membership must be verified on every request before returning or writing data - Schema/fields for AssetType should be derived from the existing database table definition out_of_scope: - Update (PUT/PATCH) endpoint for AssetType - Delete endpoint for AssetType - Role-based access control beyond authenticated project membership open_questions: - What columns/fields does the AssetType table currently have? The architect should read the schema directly from the database or migrations. - Should list-all support pagination, filtering, or sorting, or is a flat list sufficient for v1? - Should duplicate AssetType names within the same project be rejected at the API level?