summary: "Add three read/create API2 endpoints for the existing xyz.CommissioningSystem\n\ entity (exposed as \"systems\"): POST create, GET-by-id, and paginated GET list,\n\ all project-scoped under /api/v2/projects/{projectId}/systems. No new Postgres\n\ table is created \u2014 CommissioningSystem already exists in the schema. Follows\ \ the\nAssetType endpoint pattern. Revised per review: no new permissions; reuse\n\ existing Project Edit / System View permissions.\n" target_service: api2 postgres_changes: - kind: procedure schema: xyz name: usp_InsertCommissioningSystem intent: 'Create a new xyz.CommissioningSystem row for a project. Resolves _projectShardId via xyz."fn_GetProjectShardId"(_projectId), then INSERTs. CommissioningSystem columns: (ProjectShardId, CommissioningSystemId, SystemTypeId, Name, CreatedBy, IsDeleted=false). Returns the created row''s full column set so the API2 mapper can build the egress DTO in one round-trip (existence/creation happens atomically inside the proc). ' parameters: - _projectId UUID - _systemTypeId UUID - _name TEXT - _createdBy TEXT returns: 'TABLE of the created CommissioningSystem full column set: CommissioningSystemId, ProjectShardId, SystemTypeId, Name, CreatedBy, InsertedOn, LastModifiedOn, LastModifiedBy, IsDeleted, DeletedOn, DeletedBy. ' changelog_placement: Database/xyz/Procedures/usp_InsertCommissioningSystem.sql notes: 'Must resolve _projectShardId first and filter/insert by ProjectShardId, never ProjectId. Must include the standard EXCEPTION block logging to xyz."DbException". Validates the referenced SystemTypeId belongs to the same project (same ProjectShardId); raises if not. ' - kind: function schema: xyz name: fn_GetCommissioningSystem intent: "Return a single non-deleted CommissioningSystem by id, scoped to project.\n\ Resolves _projectShardId first, filters WHERE ProjectShardId = _projectShardId\n\ AND CommissioningSystemId = _commissioningSystemId AND IsDeleted = false.\nReturns\ \ zero rows if not found (controller maps to 404). This single\nfunction both\ \ enforces project-scoping (cross-project access guard) and\nreturns the entity\ \ \u2014 no separate existence check needed.\n" parameters: - _projectId UUID - _commissioningSystemId UUID returns: 'TABLE: CommissioningSystemId UUID, ProjectShardId INT, SystemTypeId UUID, Name TEXT, CreatedBy TEXT, InsertedOn TIMESTAMPTZ, LastModifiedOn TIMESTAMPTZ, LastModifiedBy TEXT, IsDeleted BOOLEAN. ' changelog_placement: Database/xyz/Functions/fn_GetCommissioningSystem.sql - kind: function schema: xyz name: fn_GetCommissioningSystemList intent: 'Return a paginated list of non-deleted CommissioningSystems for a project. Resolves _projectShardId first, filters WHERE ProjectShardId = _projectShardId AND IsDeleted = false, ordered by Id ASC for stable keyset pagination. Accepts the same pagination params as the AssetType list function so the API2 pagination contract matches exactly. ' parameters: - _projectId UUID - _lastFetchedIndexId INT - _pageSize INT returns: 'TABLE: Id INT, CommissioningSystemId UUID, ProjectShardId INT, SystemTypeId UUID, Name TEXT, CreatedBy TEXT, InsertedOn TIMESTAMPTZ, LastModifiedOn TIMESTAMPTZ, LastModifiedBy TEXT. ' changelog_placement: Database/xyz/Functions/fn_GetCommissioningSystemList.sql notes: 'Mirror the exact parameter/paging shape of the existing AssetType list function so PaginationEnvelope (records, recordCount, lastFetchedIndexId) behaves identically. The Id serial column is used as the keyset cursor. ' 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}/systems swagger_tag: Systems permissions: - PROJECT_EDIT files: - src/api/v2/projects/systems/systems.routes.ts - src/api/v2/projects/systems/systems.controller.ts - src/api/v2/projects/systems/systems.validator.ts - src/services/systems.service.ts db_calls: - usp_InsertCommissioningSystem request_dto: name: CreateSystemRequest properties: systemTypeId: type: string format: uuid required: true name: type: string max_length: 512 required: true response_dto: name: System properties: commissioningSystemId: type: string format: uuid systemTypeId: 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 notes: "Reuses the existing AssetType auth/middleware chain (verifyToken +\nproject-access\ \ guard). The project-scoping middleware validates the\nproject exists and the\ \ caller has access. usp_InsertCommissioningSystem\nvalidates the SystemTypeId\ \ belongs to the same project \u2014 controller does\nNOT make a separate lookup\ \ call. Response envelope/error handling follow\nthe AssetType create endpoint\ \ exactly.\n" - method: GET path: /api/v2/projects/{projectId}/systems/{id} swagger_tag: Systems permissions: - PROJECT_VIEW files: - src/api/v2/projects/systems/systems.routes.ts - src/api/v2/projects/systems/systems.controller.ts - src/services/systems.service.ts db_calls: - fn_GetCommissioningSystem response_dto: name: System properties: commissioningSystemId: type: string format: uuid systemTypeId: 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 notes: "fn_GetCommissioningSystem returns zero rows when the system does not exist\n\ or belongs to a different project; the controller maps that to 404\n(NotFoundError).\ \ This single call is both the cross-project access guard\nand the fetch \u2014\ \ no redundant round-trip.\n" - method: GET path: /api/v2/projects/{projectId}/systems swagger_tag: Systems permissions: - PROJECT_VIEW files: - src/api/v2/projects/systems/systems.routes.ts - src/api/v2/projects/systems/systems.controller.ts - src/services/systems.service.ts db_calls: - fn_GetCommissioningSystemList response_dto: name: PaginationEnvelope properties: records: type: array items: System recordCount: type: integer lastFetchedIndexId: type: integer notes: 'Uses parsePagingQueryParam / buildPaginatedQueryResponse exactly as the AssetType list endpoint. Pagination contract (cursor via lastFetchedIndexId + pageSize) matches AssetType precisely. ' api1_changes: none inter_service_calls: none new_permissions: none java_frozen_resources: - resource: projects confirmation: 'The "projects" resource is dual-API (present in both API2 and API1 hc-project). This feature only READS the project via the existing API2 project-access middleware and the fn_GetProjectShardId resolution inside the new procs/functions. No change is made to API1 hc-project''s ProjectResource or any hc-project System/project code. ' risks: - 'Entity-name ambiguity: the spec calls the resource "System" but the closest existing Postgres table is xyz.CommissioningSystem (there is also SystemType). This plan maps the API2 "systems" resource to xyz.CommissioningSystem. Confirm this is the intended table before approval (matches accepted open_question from run 37325fc56f80). ' - "CommissioningSystem requires a non-null SystemTypeId FK. The spec says \"no new\n\ fields are introduced\" \u2014 SystemTypeId is an existing required column, so the\n\ create request MUST accept it. Confirm callers can supply a valid SystemTypeId.\n" - 'Permission mapping uses existing PROJECT_EDIT (create) and PROJECT_VIEW (reads) per review guidance. Confirm these are the correct existing permissions for the systems surface, matching whatever AssetType uses. ' out_of_scope: - Update (PUT/PATCH) and Delete (DELETE) endpoints for systems. - Exposing System via API1 / MongoDB. - Any migration or sync with existing API1 System data. - New Postgres columns/fields on CommissioningSystem (table used as-is). testing_plan: 'Postgres: add/extend integration scenario in IntegrationTest/main.py only if the new procs/functions materially change query behavior; otherwise pure additions need no integration test change. Verify usp_InsertCommissioningSystem, fn_GetCommissioningSystem, and fn_GetCommissioningSystemList deploy clean via ./build. API2: new Mocha/Chai unit specs for systems.controller (create, get-by-id 200 and 404 cross-project guard, list pagination) and e2e specs (test/e2e/api/v2/projects/systems/systems.spec.ts) mirroring the AssetType test suite. Ensure npm test stays green. Cover: creation success, validation failure on missing/invalid systemTypeId, 404 when id belongs to another project, and pagination envelope correctness.' _meta: model: claude-opus-4-8 atom_ids: - endpoint.api2.post__api_v2_projects__projectId__upload-model - endpoint.api2.get__api_v2_projects__projectId__issues__issueId__history - endpoint.api2.get__api_v2_projects__projectId__markers__id_ - endpoint.api2.post__api_v2_projects__projectId__models__modelId__move - endpoint.api2.get__api_v2_projects__projectId__schedules - endpoint.api2.get__api_v2_projects__projectId__model-versions__modelVersionId__artefacts - endpoint.api2.get__api_v2_projects__projectId__cde-project-locations - endpoint.api2.post__api_v2_projects__projectId__devices - endpoint.api2.get__api_v2_projects__projectId__videos__videoFileId_ - endpoint.api2.get__api_v2_projects__projectId__videos - endpoint.api2.get__api_v2_projects__projectId__category-types__categoryTypeId_ - endpoint.api2.post__api_v2_projects__projectId__coordinates_upload - 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: System Entity CR Endpoints in API2 project_kind: modify user_facing_behavior: "Consumers can: (1) POST /api/v2/projects/:projectId/systems\ \ \u2014 create a new System under a project; (2) GET /api/v2/projects/:projectId/systems/:id\ \ \u2014 retrieve a single System by ID; (3) GET /api/v2/projects/:projectId/systems\ \ \u2014 retrieve a paginated list of Systems belonging to a project. Pagination,\ \ response envelope, error handling, and auth middleware must follow the same\ \ pattern as the existing AssetType endpoints." data_touched: - System - Project api_surface: both non_functional_requirements: - "Paginated list must follow the same pagination contract as AssetType (page/pageSize\ \ or cursor \u2014 match existing pattern exactly)" - 'Scoped to project: all endpoints sit under /projects/:projectId/ and must validate that the project exists and the caller has access to it' - Reuse existing API2 auth/middleware chain as used by AssetType - System entity fields should be derived from the existing partial implementation in API2 (Postgres schema); no new fields are introduced unless already present out_of_scope: - Update (PUT/PATCH) and Delete (DELETE) endpoints - Exposing System via API1 / MongoDB - Any migration or sync with existing API1 System data - New fields not already present in the existing partial System implementation open_questions: - What fields does the System entity currently have in the Postgres schema / partial API2 implementation? Architect should inspect existing migration/model before designing the response DTO. - Does the existing partial System implementation already have a DB table/model, or only a stub? Architect should confirm what's already in place to avoid duplication. - Should the project-scoping middleware reject requests where the System's projectId doesn't match the URL :projectId (i.e. cross-project access guard)? - Are there any required fields / validation rules for System creation beyond what's already defined in the partial implementation?