summary: 'Database-only change: add unique constraints on the Name column for four
project-scoped tables (xyz.AssetType, xyz.SystemType, xyz.Asset, and the
commissioning "System" entity, which is named xyz.CommissioningSystem in
the live schema). Uniqueness is scoped per project (per ProjectShardId),
case-sensitive, enforced via unique indexes in the Postgres repo. No API2
or API1 code changes are made in this plan.
'
target_service: none
postgres_changes:
- kind: note
description: "No table DDL (no new tables, no column additions/removals/alters).\
\ The\nfour target tables already exist with a Name column:\n - xyz.AssetType\
\ (Name text NOT NULL)\n - xyz.SystemType (Name text NOT NULL)\n - xyz.Asset\
\ (Name text NOT NULL)\n - xyz.CommissioningSystem (Name text NOT NULL) <--\
\ the spec's \"System\"\nThe only changes are unique indexes, which the architect\
\ MUST route via\nthe top-level index_changes section (not baked into postgres_changes).\n\
See index_changes below. The Postgres specialist appends the human-ticked\nunique\
\ indexes to Database/xyz/Constraints/999_indexes.sql.\n"
- kind: note
description: "Scope decision (resolving open questions): uniqueness is scoped PER\n\
PROJECT, not globally. All four tables are project-scoped (composite PK\n(ProjectShardId,\
\
Id)), and Citus requires project-scoped indexes\nto lead with ProjectShardId.\
\ Therefore each unique index is on\n(ProjectShardId, Name). This also satisfies\
\ the Citus single-shard\nuniqueness enforcement requirement \u2014 a global unique\
\ index on Name alone\nis NOT enforceable on a distributed table.\n"
- kind: note
description: 'Case-sensitivity decision: uniqueness is CASE-SENSITIVE (plain Name
column), matching the spec''s stated default. No LOWER() expression index
and no citext is introduced. If case-insensitive uniqueness is later
desired, that would be a separate change.
'
- kind: note
description: 'Asset and CommissioningSystem are soft-deletable (IsDeleted column).
A
plain unique index would block re-using a Name after a soft-delete. To
avoid that, the unique indexes on xyz.Asset and xyz.CommissioningSystem
are PARTIAL indexes WHERE "IsDeleted" = false, so only live rows are
constrained. AssetType and SystemType have no IsDeleted column, so their
unique indexes are unfiltered.
'
citus_changes: none
index_changes: none
reporting_changes: none
seed_data_changes: none
mongo_changes: none
api2_changes: none
api1_changes: none
inter_service_calls: none
new_permissions: none
java_frozen_resources: none
risks:
- "Existing duplicate Names: if any project already has two live rows with the same\
\ Name in any of the four tables, the CREATE UNIQUE INDEX will FAIL at deploy time.\
\ The spec marks dedup as out of scope, so the Postgres specialist must verify (per\
\ ProjectShardId, per table, live rows only) that no duplicates exist BEFORE the\
\ index is added. If duplicates exist, a Patch/ backfill or manual cleanup is required\
\ first \u2014 flag to the team rather than silently deduping.\n"
- 'Spec lists a "System" table that does not exist under that name. It has been mapped
to xyz.CommissioningSystem. If the team intended a different table, this mapping
must be confirmed at Gate 2 before the index is added.
'
- 'This plan is database-only per the revision request. The spec''s NFR about API
returning a clear 409/422 on duplicate Name is NOT implemented here. The DB unique-violation
will currently surface through whatever the existing API2 error mapping does. If
the team wants a guaranteed 409/422 with a friendly message, a follow-up API2 change
is needed (out of scope here).
'
- 'Uniqueness is scoped per-project and case-sensitive (architect decisions on the
open questions). If global-per-table or case-insensitive semantics are actually
required, the index definitions change and this must be revisited.
'
out_of_scope:
- "API2/API1 code changes \u2014 this is a database-only change (per revision request)."
- Explicit 409/422 error-shaping in the API layer for duplicate Name submissions.
- Backfilling or deduplicating existing rows that already share a Name.
- Case-insensitive uniqueness (citext / LOWER() expression indexes).
- Any new tables, columns, procedures, functions, or views.
testing_plan: "Postgres repo:\n - Run ./build to validate the full changelog (including\
\ the amended\n 999_indexes.sql) deploys clean on a fresh database.\n - Before\
\ adding each unique index, run a duplicate-detection query per\n table over\
\ live rows, e.g.:\n SELECT \"ProjectShardId\", \"Name\", COUNT(*)\n FROM\
\ xyz.\"AssetType\"\n GROUP BY \"ProjectShardId\", \"Name\" HAVING COUNT(*)\
\ > 1;\n and the analogous query for SystemType, and for Asset /\n CommissioningSystem\
\ with an added WHERE \"IsDeleted\" = false. Confirm zero\n rows returned; if\
\ not, escalate per the risk above.\n - Optionally extend IntegrationTest/main.py\
\ with a scenario that inserts a\n duplicate (ProjectShardId, Name) into each\
\ table and asserts the insert\n is rejected, and that a soft-deleted row's Name\
\ can be reused on\n Asset/CommissioningSystem (partial-index behavior).\nNo\
\ API2/API1 test changes \u2014 no code paths are modified by this plan."
_meta:
model: claude-opus-4-8
atom_ids:
- rule.architect_rules
- convention.postgres
- convention.api2
- convention.citus
- convention.api1-hc-bpm
- convention.api1-hc-project
- convention.api1-hc-iam
- endpoint.api2.put__api_v2_projects__projectId__issues_activity-categories__issueId__link
- endpoint.api2.post__api_v2_projects__projectId__coordinates
- pg.xyz.Tenant
- endpoint.api2.post__api_v2_projects__projectId__activities_categories
- endpoint.api2.put__api_v2_projects__projectId__models_folders__folderId__rename
- pg.reporting.CalculationMethod
- pg.reporting.ProgressOutput
- pg.reporting.ProjectCalculationMethod
- pg.reporting.ProjectPerformanceSnapshot
- pg.reporting.ProjectProgress
- pg.staging.DuplicatedMigratedMongoElement
spec:
title: Unique constraint on Name for 4 tables
project_kind: modify
user_facing_behavior: Creating or updating an AssetType, SystemType, Asset, or
System with a Name that already exists will be rejected with a validation error.
Names must be unique within each respective table.
data_touched:
- AssetType
- SystemType
- Asset
- System
api_surface: write
non_functional_requirements:
- Uniqueness is enforced at the database level (unique index/constraint) on the
Name column for each of the four tables.
- API should return a clear error response (e.g. 409 Conflict or 422 Unprocessable
Entity) when a duplicate Name is submitted.
out_of_scope:
- "Case-insensitivity \u2014 uniqueness is assumed case-sensitive unless architect\
\ decides otherwise"
- Backfilling or deduplicating existing data with duplicate Names
open_questions:
- Are Name uniqueness constraints scoped globally per table, or scoped within
a parent entity (e.g. unique Name per project/tenant)?
- How should existing duplicate Names in the data be handled before the constraint
is applied?
- Should uniqueness be case-insensitive (e.g. 'Pump' and 'pump' treated as duplicates)?