# Specialist Report — `agent-feature` (UNIQUE constraint on `Name`) **Spec:** `docs/specs/20260630-093618-agent-feature.md` **Status:** No code changes required in this repository (`platform-api` / api2). **Date:** 2026-06-30 --- ## TL;DR The spec asks for a **database-level `UNIQUE` constraint on the `Name` column** of `xyz."Asset"` and `xyz."CommissioningSystem"` (each scoped within its own project shard), preceded by a **backfill patch** to dedupe any pre-existing `Name` values. The spec is explicit and repeated that this is a **Postgres-only schema change with no API surface change**. After a full investigation, **this change does not belong in the api2 repository** — the schema/migration source lives in a separate database repo. I made **no production code changes** here. Doing otherwise would have contradicted the spec and fabricated scope. --- ## Files changed | File | Change | | --- | --- | | `docs/specs/20260630-093618-agent-feature.md` | Added a **Decisions** section recording why no api2 changes were made and where the change belongs. | | `_SPECIALIST_REPORT.md` | This report (new). | **No** changes to `src/`, `test/`, routes, controllers, validators, services, or swagger — by design. --- ## Why no api2 changes (evidence) 1. **The spec forbids it.** Goal: *"This is a Postgres-only schema change plus a backfill patch… No new tables, no API surface change."* The plan's API2 changes are verbatim `none`. Out of scope explicitly lists *"API2 / API1 runtime changes, validation messages, or error-handling changes."* The `files:` frontmatter is empty (`# (none listed)`). 2. **The target tables don't exist in this repo.** `grep` for `Asset` / `CommissioningSystem` (case-insensitive) across `src/` and `test/` returns **no** table references. The only related surface is the commissioning *workflows* and *system-types* endpoints, which are unrelated to the `Asset` / `CommissioningSystem` operational tables in the spec. 3. **The migration source lives in a different repository.** `docker/docker-compose.yml` mounts the schema from `${DATABASE_REPO_PATH}`: ```yaml - ${DATABASE_REPO_PATH}/PostgreSQLDatabase:/liquibase/changelog/PostgreSQLDatabase - ${DATABASE_REPO_PATH}/CitusDistributionLogic:/liquibase/changelog/Distributed ``` The local changelogs (`docker/distributed_changelog_local.xml`, `docker/patch_changelog_local.xml`) only `includeAll` paths under that mounted `PostgreSQLDatabase/Database/...` tree — none of which is checked into api2. The `xyz.""/Constraints` and backfill `Patch/*.sql` files therefore must be authored in the **database repository**, not here. 4. **The e2e seed dump doesn't touch these tables either.** `docker/test-data-dump/e2e-test-data-dump.sql` seeds ~50 `xyz.*` tables but neither `Asset` nor `CommissioningSystem`, so there is no api2-side fixture to deduplicate or guard against the new constraint. Because enforcement is explicitly required at the **database** level (and app-layer validation is out of scope), there is no faithful api2 edit to make. --- ## Recommended implementation (for the database-repo owner) This is a hand-off, **not** applied here. Two ordered Liquibase change sets per table, mirroring the existing `PostgreSQLDatabase/Database/Patch/*.sql` + `xyz/Constraints` layout. ### 1. Backfill patch — resolve duplicate `Name` values first Run as a `Patch/NNN_patch_dedupe_
_name.sql` change set **before** the constraint. Disambiguate duplicates deterministically (keep oldest, suffix the rest). Adjust the shard/scope column and tie-break column to the real schema: ```sql -- Within each project shard, suffix duplicate Names so the UNIQUE constraint can apply. WITH ranked AS ( SELECT ctid, ROW_NUMBER() OVER ( PARTITION BY "", "Name" ORDER BY "InsertedOn", ctid -- keep the earliest row unchanged ) AS rn FROM xyz."Asset" ) UPDATE xyz."Asset" a SET "Name" = a."Name" || ' (' || r.rn || ')' FROM ranked r WHERE a.ctid = r.ctid AND r.rn > 1; -- Repeat the same block for xyz."CommissioningSystem". ``` ### 2. UNIQUE constraint — include the Citus distribution column Both tables are project-scoped and **Citus-distributed**, so a UNIQUE constraint must include the distribution (shard) column — hence "scoped within its own project shard" in the spec. As a `xyz/Constraints/*.sql` change set: ```sql ALTER TABLE xyz."Asset" ADD CONSTRAINT "UQ_Asset_ProjectShard_Name" UNIQUE ("", "Name"); ALTER TABLE xyz."CommissioningSystem" ADD CONSTRAINT "UQ_CommissioningSystem_ProjectShard_Name" UNIQUE ("", "Name"); ``` Wrap each in a Liquibase `` with a matching `` (drop constraint), following the existing change sets in `distributed_changelog_local.xml` / `patch_changelog_local.xml`. --- ## Open questions for the human reviewer 1. **Confirm the change repo.** Please confirm the constraint + backfill should be authored in the database repository (`${DATABASE_REPO_PATH}` → `PostgreSQLDatabase/Database/...`). If the intent was actually to enforce uniqueness in api2 (validator/service), the spec must be amended — it currently lists that as out of scope. 2. **Distribution/shard column name.** What is the real distribution column on `xyz."Asset"` and `xyz."CommissioningSystem"` (e.g. `"ProjectId"`)? The UNIQUE constraint must include it for Citus. The SQL above uses a `` placeholder. 3. **Case sensitivity.** Spec defers case-insensitive uniqueness unless data analysis requires it. If two `Name`s differing only by case must collide, switch to a unique functional index on `(, lower("Name"))` instead. 4. **Dedupe tie-break + rename strategy.** The backfill above keeps the earliest row by `InsertedOn` and suffixes the rest with ` (n)`. Confirm the tie-break column exists and that auto-renaming duplicates (vs. failing for manual cleanup) is acceptable. --- ## Commands run None. Per the task instructions, no toolchain hunt or build/test run was performed, and there are no api2 code changes to compile or test. (The change set for the database repo would be validated by that repo's Liquibase pipeline.)